Skip to content

Commit 030bbb3

Browse files
authored
fix: deleting parameterized replaceable event before event (#354)
1 parent e4cecc8 commit 030bbb3

File tree

8 files changed

+66
-75
lines changed

8 files changed

+66
-75
lines changed

src/@types/repositories.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export interface IEventRepository {
1616
create(event: Event): Promise<number>
1717
upsert(event: Event): Promise<number>
1818
findByFilters(filters: SubscriptionFilter[]): IQueryResult<DBEvent[]>
19-
insertStubs(pubkey: string, eventIdsToDelete: EventId[]): Promise<number>
2019
deleteByPubkeyAndIds(pubkey: Pubkey, ids: EventId[]): Promise<number>
2120
}
2221

src/handlers/event-strategies/delete-event-strategy.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,10 @@ export class DeleteEventStrategy implements IEventStrategy<Event, Promise<void>>
3232
)
3333

3434
if (eventIdsToDelete.length) {
35-
const count = await this.eventRepository.deleteByPubkeyAndIds(
35+
await this.eventRepository.deleteByPubkeyAndIds(
3636
event.pubkey,
3737
eventIdsToDelete
3838
)
39-
if (!count) {
40-
await this.eventRepository.insertStubs(
41-
event.pubkey,
42-
eventIdsToDelete,
43-
)
44-
}
4539
}
4640

4741
const count = await this.eventRepository.create(event)

src/repositories/event-repository.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
forEach,
1111
forEachObjIndexed,
1212
groupBy,
13-
identity,
1413
ifElse,
1514
invoker,
1615
is,
@@ -229,6 +228,7 @@ export class EventRepository implements IEventRepository {
229228
prop(EventExpirationTimeMetadataKey as any),
230229
always(null),
231230
),
231+
deleted_at: always(null),
232232
})(event)
233233

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

253-
public insertStubs(pubkey: string, eventIdsToDelete: EventId[]): Promise<number> {
254-
debug('inserting stubs for %s: %o', pubkey, eventIdsToDelete)
255-
const date = new Date()
256-
return this.masterDbClient('events').insert(
257-
eventIdsToDelete.map(
258-
applySpec({
259-
event_id: pipe(identity, toBuffer),
260-
event_pubkey: pipe(always(pubkey), toBuffer),
261-
event_created_at: always(Math.floor(date.getTime() / 1000)),
262-
event_kind: always(5),
263-
event_tags: always('[]'),
264-
event_content: always(''),
265-
event_signature: pipe(always(''), toBuffer),
266-
event_delegator: always(null),
267-
event_deduplication: pipe(always([pubkey, 5]), toJSON),
268-
expires_at: always(null),
269-
deleted_at: always(date.toISOString()),
270-
})
271-
)
272-
)
273-
.onConflict()
274-
.ignore() as Promise<any>
275-
}
276-
277253
public deleteByPubkeyAndIds(pubkey: string, eventIdsToDelete: EventId[]): Promise<number> {
278254
debug('deleting events from %s: %o', pubkey, eventIdsToDelete)
279255

test/integration/features/nip-09/nip-09.feature

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,10 @@ Feature: NIP-09
2121
And Alice drafts a text_note event with content "Twitter > Nostr"
2222
When Alice sends a delete event for their last event
2323
And Alice sends their last draft event successfully
24-
And Alice subscribes to author Alice
25-
Then Alice receives 1 delete event from Alice and EOSE
2624

2725
Scenario: Alice sends a delete before deleted set_metadata
2826
Given someone called Alice
2927
And someone called Bob
3028
And Alice drafts a set_metadata event
3129
When Alice sends a delete event for their last event
32-
And Alice sends their last draft event unsuccessfully
33-
And Alice subscribes to author Alice
34-
Then Alice receives 1 delete event from Alice and EOSE
30+
Then Alice sends their last draft event successfully

test/integration/features/nip-33/nip-33.feature

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,24 @@ Feature: NIP-33 Parameterized replaceable events
3030
And Alice sends a parameterized_replaceable_event_1 event with content "third" and tag d containing "friends"
3131
And Bob subscribes to author Alice
3232
Then Bob receives a parameterized_replaceable_event_1 event from Alice with content "third" and tag d containing "friends"
33+
34+
Scenario: Alice deletes a parameterized replaceable event
35+
Given someone called Alice
36+
When Alice sends a parameterized_replaceable_event_1 event with content "exercise" and tag d containing "2023-resolutions"
37+
And Alice sends a delete event for their last event
38+
And Alice subscribes to author Alice
39+
Then Alice receives 1 delete event from Alice and EOSE
40+
41+
Scenario: Alice deletes and replaces a parameterized replaceable event
42+
Given someone called Alice
43+
And Alice sends a parameterized_replaceable_event_1 event with content "gym" and tag d containing "2024-resolutions"
44+
And Alice sends a delete event for their last event
45+
When Alice sends a parameterized_replaceable_event_1 event with content "exercise" and tag d containing "2024-resolutions"
46+
And Alice subscribes to parameterized_replaceable_event_1 events from Alice
47+
Then Alice receives a parameterized_replaceable_event_1 event from Alice with content "exercise" and tag d containing "2024-resolutions"
48+
49+
Scenario: Alice deletes before sending parameterized replaceable event
50+
Given someone called Alice
51+
And Alice drafts a parameterized_replaceable_event_2 event with content "don't worry about it" and tag d containing "topsycrets"
52+
When Alice sends a delete event for their last event
53+
And Alice sends their last draft event successfully

test/integration/features/nip-33/nip-33.feature.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Then, When } from '@cucumber/cucumber'
1+
import { Then, When, World } from '@cucumber/cucumber'
22
import { expect } from 'chai'
33
import WebSocket from 'ws'
44

5-
import { createEvent, sendEvent, waitForEventCount, waitForNextEvent } from '../helpers'
5+
import { createEvent, createSubscription, sendEvent, waitForEventCount, waitForNextEvent } from '../helpers'
66
import { EventKinds, EventTags } from '../../../../src/constants/base'
77
import { Event } from '../../../../src/@types/event'
8+
import { isDraft } from '../shared'
89

910
When(/^(\w+) sends a parameterized_replaceable_event_0 event with content "([^"]+)" and tag (\w) containing "([^"]+)"$/, async function(
1011
name: string,
@@ -108,3 +109,40 @@ Then(/(\w+) receives (\d+) parameterized_replaceable_event_0 events? from (\w+)
108109
expect(events[0].pubkey).to.equal(this.parameters.identities[author].pubkey)
109110
expect(events[0].content).to.equal(content)
110111
})
112+
113+
When(/^(\w+) subscribes to parameterized_replaceable_event_1 events from (\w+)$/, async function (this: World<Record<string, any>>, name: string, author: string) {
114+
const ws = this.parameters.clients[name] as WebSocket
115+
const authorPubkey = this.parameters.identities[author].pubkey
116+
const subscription = {
117+
name: `test-${Math.random()}`,
118+
filters: [
119+
{ kinds: [EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 1], authors: [authorPubkey] },
120+
],
121+
}
122+
this.parameters.subscriptions[name].push(subscription)
123+
124+
await createSubscription(ws, subscription.name, subscription.filters)
125+
})
126+
127+
128+
Then(/^(\w+) drafts a parameterized_replaceable_event_2 event with content "([^"]+?)" and tag (\w+) containing "([^"]+?)"$/, async function (
129+
name: string,
130+
content: string,
131+
tagName: string,
132+
tagValue: string,
133+
) {
134+
const { pubkey, privkey } = this.parameters.identities[name]
135+
136+
const event: Event = await createEvent({
137+
pubkey,
138+
kind: EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 2,
139+
content,
140+
tags: [
141+
[tagName, tagValue],
142+
],
143+
}, privkey)
144+
145+
event[isDraft] = true
146+
147+
this.parameters.events[name].push(event)
148+
})

test/unit/handlers/event-strategies/delete-event-strategy.spec.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('DeleteEventStrategy', () => {
3232
let webSocketEmitStub: Sinon.SinonStub
3333
let eventRepositoryCreateStub: Sinon.SinonStub
3434
let eventRepositoryDeleteByPubkeyAndIdsStub: Sinon.SinonStub
35-
let eventRepositoryInsertStubsStub: Sinon.SinonStub
3635

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

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

4443
eventRepositoryCreateStub = sandbox.stub(EventRepository.prototype, 'create')
4544
eventRepositoryDeleteByPubkeyAndIdsStub = sandbox.stub(EventRepository.prototype, 'deleteByPubkeyAndIds')
46-
eventRepositoryInsertStubsStub = sandbox.stub(EventRepository.prototype, 'insertStubs')
4745

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

70-
it('inserts stubs', async () => {
71-
await strategy.execute(event)
72-
73-
expect(eventRepositoryInsertStubsStub).to.have.been.calledOnceWithExactly(
74-
event.pubkey,
75-
[
76-
'0000000000000000000000000000000000000000000000000000000000000000',
77-
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
78-
]
79-
)
80-
})
81-
8268
it('deletes events if it has e tags', async () => {
8369
await strategy.execute(event)
8470

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

136122
expect(eventRepositoryCreateStub).to.have.been.calledOnceWithExactly(event)
137123
expect(eventRepositoryDeleteByPubkeyAndIdsStub).not.to.have.been.called
138-
expect(eventRepositoryInsertStubsStub).to.not.have.been.called
139124
expect(webSocketEmitStub).not.to.have.been.called
140125
})
141126
})

test/unit/repositories/event-repository.spec.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -439,24 +439,6 @@ describe('EventRepository', () => {
439439
})
440440
})
441441

442-
describe('insertStubs', () => {
443-
let clock: sinon.SinonFakeTimers
444-
445-
beforeEach(() => {
446-
clock = sinon.useFakeTimers(1673835425)
447-
})
448-
449-
afterEach(() => {
450-
clock.restore()
451-
})
452-
453-
it('insert stubs by pubkey & event ids', () => {
454-
const query = repository.insertStubs('001122', ['aabbcc', 'ddeeff']).toString()
455-
456-
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')
457-
})
458-
})
459-
460442
describe('deleteByPubkeyAndIds', () => {
461443
it('marks event as deleted by pubkey & event_id if not deleted', () => {
462444
const query = repository.deleteByPubkeyAndIds('001122', ['aabbcc', 'ddeeff']).toString()
@@ -480,7 +462,7 @@ describe('EventRepository', () => {
480462

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

483-
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":"[email protected]","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":"[email protected]","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')
465+
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":"[email protected]","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":"[email protected]","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')
484466
})
485467

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

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

501-
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":"[email protected]","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":"[email protected]","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')
483+
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":"[email protected]","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":"[email protected]","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')
502484
})
503485
})
504486
})

0 commit comments

Comments
 (0)