✨ 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,
|
isAddressableEvent,
|
||||||
isArray,
|
isArray,
|
||||||
isCCNReplaceableEvent,
|
isCCNReplaceableEvent,
|
||||||
|
isDeleteEvent,
|
||||||
isLocalhost,
|
isLocalhost,
|
||||||
isReplaceableEvent,
|
isReplaceableEvent,
|
||||||
isValidJSON,
|
isValidJSON,
|
||||||
|
@ -135,6 +136,11 @@ function addEventToDb(
|
||||||
|
|
||||||
if (existingEvent) throw new EventAlreadyExistsException();
|
if (existingEvent) throw new EventAlreadyExistsException();
|
||||||
|
|
||||||
|
if (isDeleteEvent(decryptedEvent.kind)) {
|
||||||
|
handleDeletionEvent(decryptedEvent, encryptedEvent, ccnPubkey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isInvite =
|
const isInvite =
|
||||||
decryptedEvent.tags.findIndex(
|
decryptedEvent.tags.findIndex(
|
||||||
(tag: string[]) => tag[0] === 'type' && tag[1] === 'invite',
|
(tag: string[]) => tag[0] === 'type' && tag[1] === 'invite',
|
||||||
|
@ -486,6 +492,8 @@ function filtersMatchingEvent(
|
||||||
event: NostrEvent,
|
event: NostrEvent,
|
||||||
connection: UserConnection,
|
connection: UserConnection,
|
||||||
): string[] {
|
): string[] {
|
||||||
|
if (isDeleteEvent(event.kind)) return [];
|
||||||
|
|
||||||
const matching = [];
|
const matching = [];
|
||||||
for (const subscription of connection.subscriptions.keys()) {
|
for (const subscription of connection.subscriptions.keys()) {
|
||||||
const filters = connection.subscriptions.get(subscription);
|
const filters = connection.subscriptions.get(subscription);
|
||||||
|
@ -530,7 +538,7 @@ function handleRequest(connection: UserConnection, request: NostrClientREQ) {
|
||||||
return log.warn('No active CCN found');
|
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) => {
|
const filtersAreNotEmpty = filters.some((filter) => {
|
||||||
return Object.values(filter).some((value) => {
|
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({
|
Deno.serve({
|
||||||
port: 6942,
|
port: 6942,
|
||||||
handler: (request) => {
|
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 {
|
export function isCCNReplaceableEvent(kind: number): boolean {
|
||||||
return kind >= 60000 && kind < 65536;
|
return kind >= 60000 && kind < 65536;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue