diff --git a/index.ts b/index.ts index 104eaba..49a886b 100644 --- a/index.ts +++ b/index.ts @@ -31,6 +31,7 @@ import { isAddressableEvent, isArray, isCCNReplaceableEvent, + isDeleteEvent, isLocalhost, isReplaceableEvent, isValidJSON, @@ -135,6 +136,11 @@ function addEventToDb( if (existingEvent) throw new EventAlreadyExistsException(); + if (isDeleteEvent(decryptedEvent.kind)) { + handleDeletionEvent(decryptedEvent, encryptedEvent, ccnPubkey); + return; + } + const isInvite = decryptedEvent.tags.findIndex( (tag: string[]) => tag[0] === 'type' && tag[1] === 'invite', @@ -486,6 +492,8 @@ function filtersMatchingEvent( event: NostrEvent, connection: UserConnection, ): string[] { + if (isDeleteEvent(event.kind)) return []; + const matching = []; for (const subscription of connection.subscriptions.keys()) { const filters = connection.subscriptions.get(subscription); @@ -530,7 +538,7 @@ function handleRequest(connection: UserConnection, request: NostrClientREQ) { return log.warn('No active CCN found'); } - let query = sqlPartial`SELECT * FROM events WHERE replaced = 0 AND ccn_pubkey = ${activeCCN.pubkey}`; + let query = sqlPartial`SELECT * FROM events WHERE replaced = 0 AND deleted = 0 AND ccn_pubkey = ${activeCCN.pubkey}`; const filtersAreNotEmpty = filters.some((filter) => { return Object.values(filter).some((value) => { @@ -1041,6 +1049,122 @@ function handleCCNCommands( } } +function handleDeletionEvent( + deletionEvent: nostrTools.VerifiedEvent, + encryptedEvent: nostrTools.VerifiedEvent, + ccnPubkey: string, +) { + const eventIds: string[] = []; + const aTagRefs: { kind: number; pubkey: string; dTag?: string }[] = []; + + for (const tag of deletionEvent.tags) { + if (tag[0] === 'e' && tag[1]) { + eventIds.push(tag[1]); + } else if (tag[0] === 'a' && tag[1]) { + const parsedATag = parseATagQuery(tag[1]); + if (parsedATag.kind && parsedATag.pubkey) { + aTagRefs.push(parsedATag); + } + } + } + + if (eventIds.length === 0 && aTagRefs.length === 0) { + return; + } + + try { + db.run('BEGIN TRANSACTION'); + + sql` + INSERT INTO events (id, original_id, pubkey, created_at, kind, content, sig, first_seen, ccn_pubkey) VALUES ( + ${deletionEvent.id}, + ${encryptedEvent.id}, + ${deletionEvent.pubkey}, + ${deletionEvent.created_at}, + ${deletionEvent.kind}, + ${deletionEvent.content}, + ${deletionEvent.sig}, + unixepoch(), + ${ccnPubkey} + ) + `(db); + + if (deletionEvent.tags) { + for (let i = 0; i < deletionEvent.tags.length; i++) { + const tag = sql` + INSERT INTO event_tags(event_id, tag_name, tag_index) VALUES ( + ${deletionEvent.id}, + ${deletionEvent.tags[i][0]}, + ${i} + ) RETURNING tag_id + `(db)[0]; + + for (let j = 1; j < deletionEvent.tags[i].length; j++) { + sql` + INSERT INTO event_tags_values(tag_id, value_position, value) VALUES ( + ${tag.tag_id}, + ${j}, + ${deletionEvent.tags[i][j]} + ) + `(db); + } + } + } + + if (eventIds.length > 0) { + sql` + UPDATE events + SET deleted = 1 + WHERE id IN (${eventIds}) + AND pubkey = ${deletionEvent.pubkey} + AND ccn_pubkey = ${ccnPubkey} + `(db); + } + + if (aTagRefs.length > 0) { + const withDTag = aTagRefs.filter((ref) => ref.dTag); + const withoutDTag = aTagRefs.filter((ref) => !ref.dTag); + + if (withoutDTag.length > 0) { + const kinds = withoutDTag.map((ref) => ref.kind); + sql` + UPDATE events + SET deleted = 1 + WHERE kind IN (${kinds}) + AND pubkey = ${deletionEvent.pubkey} + AND ccn_pubkey = ${ccnPubkey} + AND created_at <= ${deletionEvent.created_at} + `(db); + } + + for (const aTagRef of withDTag) { + sql` + UPDATE events + SET deleted = 1 + WHERE kind = ${aTagRef.kind} + AND pubkey = ${deletionEvent.pubkey} + AND ccn_pubkey = ${ccnPubkey} + AND created_at <= ${deletionEvent.created_at} + AND id IN ( + SELECT event_id FROM event_tags + WHERE tag_name = 'd' + AND tag_id IN ( + SELECT tag_id FROM event_tags_values + WHERE value_position = 1 + AND value = ${aTagRef.dTag} + ) + ) + `(db); + } + } + + db.run('COMMIT TRANSACTION'); + } catch (e) { + db.run('ROLLBACK TRANSACTION'); + throw e; + } +} + Deno.serve({ port: 6942, handler: (request) => { diff --git a/migrations/7-addDeletedStatus.sql b/migrations/7-addDeletedStatus.sql new file mode 100644 index 0000000..15daba9 --- /dev/null +++ b/migrations/7-addDeletedStatus.sql @@ -0,0 +1,2 @@ +ALTER TABLE events ADD COLUMN deleted INTEGER DEFAULT 0; +CREATE INDEX idx_events_deleted ON events(deleted); diff --git a/utils.ts b/utils.ts index 7c67aa5..ac73f39 100644 --- a/utils.ts +++ b/utils.ts @@ -127,6 +127,10 @@ export function isRegularEvent(kind: number): boolean { ); } +export function isDeleteEvent(kind: number): boolean { + return kind === 5; +} + export function isCCNReplaceableEvent(kind: number): boolean { return kind >= 60000 && kind < 65536; }