✨ feat: add support for NIP-09 deletion events and filtering deleted events
This commit is contained in:
parent
36c7401fa8
commit
da442cabff
3 changed files with 131 additions and 1 deletions
126
index.ts
126
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) => {
|
||||
|
|
2
migrations/7-addDeletedStatus.sql
Normal file
2
migrations/7-addDeletedStatus.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE events ADD COLUMN deleted INTEGER DEFAULT 0;
|
||||
CREATE INDEX idx_events_deleted ON events(deleted);
|
4
utils.ts
4
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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue