Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: deleting parameterized replaceable event before event #354

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
})
})
})
Loading