Skip to content

Commit

Permalink
feat: upsert kind 41 events
Browse files Browse the repository at this point in the history
  • Loading branch information
cameri committed Dec 26, 2022
1 parent afbda70 commit 41ae842
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/repositories/event-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class EventRepository implements IEventRepository {
// NIP-33: Parameterized Replaceable Events
.onConflict(
this.dbClient.raw(
'(event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 OR (event_kind >= 10000 AND event_kind < 20000)) OR (event_kind >= 30000 AND event_kind < 40000)'
'(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)'
)
)
.merge(omit(['event_pubkey', 'event_kind', 'event_deduplication'])(row))
Expand Down
1 change: 1 addition & 0 deletions src/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export const isEventSignatureValid = async (event: Event): Promise<boolean> => {
export const isReplaceableEvent = (event: Event): boolean => {
return event.kind === EventKinds.SET_METADATA
|| event.kind === EventKinds.CONTACT_LIST
|| event.kind === EventKinds.CHANNEL_METADATA
|| (event.kind >= EventKinds.REPLACEABLE_FIRST && event.kind <= EventKinds.REPLACEABLE_LAST)
}

Expand Down
38 changes: 38 additions & 0 deletions test/integration/features/nip-28/nip-28.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Feature: NIP-28
Scenario: Alice creates a channel
Given someone called Alice
When Alice sends a channel_creation event with content '{\"name\": \"Demo Channel\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice subscribes to last event from Alice
Then Alice receives a channel_creation event from Alice with content '{\"name\": \"Demo Channel\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'

Scenario: Alice sets metadata for a channel
Given someone called Alice
And Alice subscribes to author Alice
And Alice sends a channel_creation event with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice receives a channel_creation event from Alice with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
When Alice sends a channel_metadata event with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'
Then Alice receives a channel_metadata event from Alice with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'

Scenario: Alice replaces metadata for a channel
Given someone called Alice
And Alice subscribes to author Alice
And Alice sends a channel_creation event with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice receives a channel_creation event from Alice with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice sends a channel_metadata event with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'
And Alice receives a channel_metadata event from Alice with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'
When Alice sends a channel_metadata event with content '{\"name\": \"Replaced\", \"about\": \"A different test channel.\", \"picture\": \"https://placekitten.com/400/400\"}'
Then Alice receives a channel_metadata event from Alice with content '{\"name\": \"Replaced\", \"about\": \"A different test channel.\", \"picture\": \"https://placekitten.com/400/400\"}'

Scenario: Alice replaces metadata for a channel
Given someone called Alice
And Alice subscribes to author Alice
And Alice sends a channel_creation event with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice receives a channel_creation event from Alice with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice sends a channel_metadata event with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'
And Alice receives a channel_metadata event from Alice with content '{\"name\": \"New\", \"about\": \"A better test channel.\", \"picture\": \"https://placekitten.com/256/256\"}'
And Alice unsubscribes from author Alice
When Alice sends a channel_metadata event with content '{\"name\": \"Replaced\", \"about\": \"A different test channel.\", \"picture\": \"https://placekitten.com/400/400\"}'
And Alice subscribes to channel_creation events
And Alice receives a channel_creation event from Alice with content '{\"name\": \"Original\", \"about\": \"A test channel.\", \"picture\": \"https://placekitten.com/200/200\"}'
And Alice subscribes to channel_metadata events
Then Alice receives a channel_metadata event from Alice with content '{\"name\": \"Replaced\", \"about\": \"A different test channel.\", \"picture\": \"https://placekitten.com/400/400\"}'
72 changes: 72 additions & 0 deletions test/integration/features/nip-28/nip-28.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Before, Then, When, World } from '@cucumber/cucumber'
import WebSocket from 'ws'

import { createEvent, createSubscription, sendEvent, waitForNextEvent } from '../helpers'
import { Event } from '../../../../src/@types/event'
import { expect } from 'chai'

Before(function () {
this.parameters.channels = []
})

When(/^(\w+) sends a channel_creation event with content '([^']+)'$/, async function(name: string, content: string) {
const ws = this.parameters.clients[name] as WebSocket
const { pubkey, privkey } = this.parameters.identities[name]

const event: Event = await createEvent({ pubkey, kind: 40, content }, privkey)
this.parameters.channels.push(event.id)
await sendEvent(ws, event)
this.parameters.events[name].push(event)
})

When(/^(\w+) sends a channel_metadata event with content '([^']+)'$/, async function(name: string, content: string) {
const ws = this.parameters.clients[name] as WebSocket
const { pubkey, privkey } = this.parameters.identities[name]

const channel = this.parameters.channels[this.parameters.channels.length - 1]
const event: Event = await createEvent({ pubkey, kind: 41, content, tags: [['e', channel]] }, privkey)

await sendEvent(ws, event)
this.parameters.events[name].push(event)
})

Then(/(\w+) receives a channel_creation event from (\w+) with content '([^']+?)'/, async function(name: string, author: string, content: string) {
const ws = this.parameters.clients[name] as WebSocket
const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1]
const receivedEvent = await waitForNextEvent(ws, subscription.name)

expect(receivedEvent.kind).to.equal(40)
expect(receivedEvent.pubkey).to.equal(this.parameters.identities[author].pubkey)
expect(receivedEvent.content).to.equal(content)
})


Then(/(\w+) receives a channel_metadata event from (\w+) with content '([^']+?)'/, async function(name: string, author: string, content: string) {
const ws = this.parameters.clients[name] as WebSocket
const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1]
const receivedEvent = await waitForNextEvent(ws, subscription.name)

const channel = this.parameters.channels[this.parameters.channels.length - 1]

expect(receivedEvent.kind).to.equal(41)
expect(receivedEvent.pubkey).to.equal(this.parameters.identities[author].pubkey)
expect(receivedEvent.content).to.equal(content)
expect(receivedEvent.tags).to.deep.include(['e', channel])
})

When(/^(\w+) subscribes to channel_creation events$/, async function(this: World<Record<string, any>>, name: string) {
const ws = this.parameters.clients[name] as WebSocket
const subscription = { name: `test-${Math.random()}`, filters: [{ kinds: [40] }] }
this.parameters.subscriptions[name].push(subscription)

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


When(/^(\w+) subscribes to channel_metadata events$/, async function(this: World<Record<string, any>>, name: string) {
const ws = this.parameters.clients[name] as WebSocket
const subscription = { name: `test-${Math.random()}`, filters: [{ kinds: [41] }] }
this.parameters.subscriptions[name].push(subscription)

await createSubscription(ws, subscription.name, subscription.filters)
})
4 changes: 2 additions & 2 deletions test/unit/repositories/event-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,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") 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\', \'[]\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 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 where "events"."event_created_at" < 1564498626')
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") 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\', \'[]\') 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 where "events"."event_created_at" < 1564498626')
})

it('replaces event based on event_pubkey, event_kind and event_deduplication', () => {
Expand All @@ -476,7 +476,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") 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\', \'[]\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 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 where "events"."event_created_at" < 1564498626')
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") 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\', \'[]\') 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 where "events"."event_created_at" < 1564498626')
})
})
})

0 comments on commit 41ae842

Please sign in to comment.