Browse Source

Enhance delete event functionality and UI updates

- Improved logging in handle-delete.go for admin and owner checks during delete operations.
- Updated handle-event.go to ensure delete events are saved before processing, with enhanced error handling.
- Added fetchDeleteEventsByTarget function in nostr.js to retrieve delete events targeting specific event IDs.
- Modified App.svelte to include delete event verification and improved event sorting by creation timestamp.
- Enhanced UI to display delete event information and added loading indicators for event refresh actions.
main
mleku 3 months ago
parent
commit
40f3cb6f6e
No known key found for this signature in database
  1. 9
      app/handle-delete.go
  2. 36
      app/handle-event.go
  3. 186
      app/web/src/App.svelte
  4. 39
      app/web/src/nostr.js
  5. 113
      pkg/database/query-events.go

9
app/handle-delete.go

@ -30,6 +30,15 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { @@ -30,6 +30,15 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
log.I.F("HandleDelete: tag %d: %s = %s", i, string(t.Key()), string(t.Value()))
}
// Debug: log admin and owner lists
log.I.F("HandleDelete: checking against %d admins and %d owners", len(l.Admins), len(l.Owners))
for i, pk := range l.Admins {
log.I.F("HandleDelete: admin[%d] = %0x", i, pk)
}
for i, pk := range l.Owners {
log.I.F("HandleDelete: owner[%d] = %0x", i, pk)
}
var ownerDelete bool
for _, pk := range l.Admins {
if utils.FastEqual(pk, env.E.Pubkey) {

36
app/handle-event.go

@ -199,8 +199,13 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { @@ -199,8 +199,13 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
// if the event is a delete, process the delete
if env.E.Kind == kind.EventDeletion.K {
log.I.F("processing delete event %0x", env.E.ID)
if err = l.HandleDelete(env); err != nil {
log.E.F("HandleDelete failed for event %0x: %v", env.E.ID, err)
// Store the delete event itself FIRST to ensure it's available for queries
saveCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
log.I.F("attempting to save delete event %0x from pubkey %0x", env.E.ID, env.E.Pubkey)
if _, _, err = l.SaveEvent(saveCtx, env.E); err != nil {
log.E.F("failed to save delete event %0x: %v", env.E.ID, err)
if strings.HasPrefix(err.Error(), "blocked:") {
errStr := err.Error()[len("blocked: "):len(err.Error())]
if err = Ok.Error(
@ -210,19 +215,14 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { @@ -210,19 +215,14 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
}
return
}
// For non-blocked errors, still send OK but log the error
log.W.F("Delete processing failed but continuing: %v", err)
} else {
log.I.F("HandleDelete completed successfully for event %0x", env.E.ID)
}
// Send OK response for delete events
if err = Ok.Ok(l, env, ""); chk.E(err) {
chk.E(err)
return
}
// Store the delete event itself
saveCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if _, _, err = l.SaveEvent(saveCtx, env.E); err != nil {
log.I.F("successfully saved delete event %0x", env.E.ID)
// Now process the deletion (remove target events)
if err = l.HandleDelete(env); err != nil {
log.E.F("HandleDelete failed for event %0x: %v", env.E.ID, err)
if strings.HasPrefix(err.Error(), "blocked:") {
errStr := err.Error()[len("blocked: "):len(err.Error())]
if err = Ok.Error(
@ -232,9 +232,17 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { @@ -232,9 +232,17 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
}
return
}
chk.E(err)
// For non-blocked errors, still send OK but log the error
log.W.F("Delete processing failed but continuing: %v", err)
} else {
log.I.F("HandleDelete completed successfully for event %0x", env.E.ID)
}
// Send OK response for delete events
if err = Ok.Ok(l, env, ""); chk.E(err) {
return
}
// Deliver the delete event to subscribers
clonedEvent := env.E.Clone()
go l.publishers.Deliver(clonedEvent)

186
app/web/src/App.svelte

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script>
import LoginModal from './LoginModal.svelte';
import { initializeNostrClient, fetchUserProfile, fetchAllEvents, fetchUserEvents, searchEvents, fetchEventById, nostrClient, NostrClient } from './nostr.js';
import { initializeNostrClient, fetchUserProfile, fetchAllEvents, fetchUserEvents, searchEvents, fetchEventById, fetchDeleteEventsByTarget, nostrClient, NostrClient } from './nostr.js';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
let isDarkTheme = false;
@ -147,6 +147,7 @@ @@ -147,6 +147,7 @@
}
function truncatePubkey(pubkey) {
if (!pubkey) return 'unknown';
return pubkey.slice(0, 8) + '...' + pubkey.slice(-8);
}
@ -173,10 +174,12 @@ @@ -173,10 +174,12 @@
await loadAllEvents(true, authors);
}
// Events are filtered server-side, but add client-side filtering as backup
$: filteredEvents = showOnlyMyEvents && isLoggedIn && userPubkey
? allEvents.filter(event => event.pubkey === userPubkey)
: allEvents;
// Sort events by created_at timestamp (newest first)
$: filteredEvents = (showOnlyMyEvents && isLoggedIn && userPubkey
? allEvents.filter(event => event.pubkey && event.pubkey === userPubkey)
: allEvents).sort((a, b) => b.created_at - a.created_at);
async function deleteEvent(eventId) {
if (!isLoggedIn) {
@ -193,7 +196,7 @@ @@ -193,7 +196,7 @@
// Check permissions: admin/owner can delete any event, write users can only delete their own events
const canDelete = (userRole === 'admin' || userRole === 'owner') ||
(userRole === 'write' && event.pubkey === userPubkey);
(userRole === 'write' && event.pubkey && event.pubkey === userPubkey);
if (!canDelete) {
alert('You do not have permission to delete this event');
@ -229,7 +232,7 @@ @@ -229,7 +232,7 @@
// Only publish to external relays if:
// 1. User is deleting their own event, OR
// 2. User is admin/owner AND deleting their own event
const isDeletingOwnEvent = event.pubkey === userPubkey;
const isDeletingOwnEvent = event.pubkey && event.pubkey === userPubkey;
const isAdminOrOwner = (userRole === 'admin' || userRole === 'owner');
const shouldPublishToExternalRelays = isDeletingOwnEvent;
@ -255,6 +258,25 @@ @@ -255,6 +258,25 @@
console.log('Could not fetch event after deletion (likely deleted):', fetchError.message);
}
// Also verify that the delete event has been saved
try {
const deleteEvents = await fetchDeleteEventsByTarget(eventId, { timeout: 5000 });
if (deleteEvents.length > 0) {
console.log(`Delete event verification: Found ${deleteEvents.length} delete event(s) targeting ${eventId}`);
// Check if our delete event is among them
const ourDeleteEvent = deleteEvents.find(de => de.pubkey && de.pubkey === userPubkey);
if (ourDeleteEvent) {
console.log('Our delete event found in database:', ourDeleteEvent.id);
} else {
console.warn('Our delete event not found in database, but other delete events exist');
}
} else {
console.warn('No delete events found in database for target event:', eventId);
}
} catch (deleteFetchError) {
console.log('Could not verify delete event in database:', deleteFetchError.message);
}
// Remove from local lists
allEvents = allEvents.filter(event => event.id !== eventId);
myEvents = myEvents.filter(event => event.id !== eventId);
@ -273,6 +295,11 @@ @@ -273,6 +295,11 @@
// Update persistent state
savePersistentState();
// Reload events to show the new delete event at the top
console.log('Reloading events to show delete event...');
const authors = showOnlyMyEvents && isLoggedIn && userPubkey ? [userPubkey] : null;
await loadAllEvents(true, authors);
alert(`Event deleted successfully (accepted by ${result.okCount} relay(s))`);
} else {
throw new Error('No relays accepted the delete event');
@ -306,6 +333,25 @@ @@ -306,6 +333,25 @@
console.log('Could not fetch event after deletion (likely deleted):', fetchError.message);
}
// Also verify that the delete event has been saved
try {
const deleteEvents = await fetchDeleteEventsByTarget(eventId, { timeout: 5000 });
if (deleteEvents.length > 0) {
console.log(`Delete event verification: Found ${deleteEvents.length} delete event(s) targeting ${eventId}`);
// Check if our delete event is among them
const ourDeleteEvent = deleteEvents.find(de => de.pubkey && de.pubkey === userPubkey);
if (ourDeleteEvent) {
console.log('Our delete event found in database:', ourDeleteEvent.id);
} else {
console.warn('Our delete event not found in database, but other delete events exist');
}
} else {
console.warn('No delete events found in database for target event:', eventId);
}
} catch (deleteFetchError) {
console.log('Could not verify delete event in database:', deleteFetchError.message);
}
// Remove from local lists
allEvents = allEvents.filter(event => event.id !== eventId);
myEvents = myEvents.filter(event => event.id !== eventId);
@ -324,6 +370,11 @@ @@ -324,6 +370,11 @@
// Update persistent state
savePersistentState();
// Reload events to show the new delete event at the top
console.log('Reloading events to show delete event...');
const authors = showOnlyMyEvents && isLoggedIn && userPubkey ? [userPubkey] : null;
await loadAllEvents(true, authors);
alert(`Event deleted successfully (local relay only - admin/owner deleting other user's event)`);
} else {
throw new Error('Local relay did not accept the delete event');
@ -463,7 +514,7 @@ @@ -463,7 +514,7 @@
function updateGlobalCache(events) {
globalEventsCache = events;
globalEventsCache = events.sort((a, b) => b.created_at - a.created_at);
globalCacheTimestamp = Date.now();
savePersistentState();
}
@ -943,9 +994,9 @@ @@ -943,9 +994,9 @@
console.log('Received search results:', events.length, 'events');
if (reset) {
searchResult.events = events;
searchResult.events = events.sort((a, b) => b.created_at - a.created_at);
} else {
searchResult.events = [...searchResult.events, ...events];
searchResult.events = [...searchResult.events, ...events].sort((a, b) => b.created_at - a.created_at);
}
// Update oldest timestamp for next pagination
@ -1172,9 +1223,9 @@ @@ -1172,9 +1223,9 @@
});
if (reset) {
myEvents = events;
myEvents = events.sort((a, b) => b.created_at - a.created_at);
} else {
myEvents = [...myEvents, ...events];
myEvents = [...myEvents, ...events].sort((a, b) => b.created_at - a.created_at);
}
// Update oldest timestamp for next pagination
@ -1248,9 +1299,9 @@ @@ -1248,9 +1299,9 @@
}
try {
// Use WebSocket REQ to fetch events with timestamp-based pagination
// Use Nostr WebSocket to fetch events with timestamp-based pagination
// Load 100 events on initial load, otherwise use 200 for pagination
console.log('Loading events with authors filter:', authors);
console.log('Loading events with authors filter:', authors, 'including delete events');
const events = await fetchAllEvents({
limit: reset ? 100 : 200,
until: reset ? Math.floor(Date.now() / 1000) : oldestEventTimestamp,
@ -1258,18 +1309,18 @@ @@ -1258,18 +1309,18 @@
});
console.log('Received events:', events.length, 'events');
if (authors && events.length > 0) {
const nonUserEvents = events.filter(event => event.pubkey !== userPubkey);
const nonUserEvents = events.filter(event => event.pubkey && event.pubkey !== userPubkey);
if (nonUserEvents.length > 0) {
console.warn('Server returned non-user events:', nonUserEvents.length, 'out of', events.length);
}
}
if (reset) {
allEvents = events;
allEvents = events.sort((a, b) => b.created_at - a.created_at);
// Update global cache
updateGlobalCache(events);
} else {
allEvents = [...allEvents, ...events];
allEvents = [...allEvents, ...events].sort((a, b) => b.created_at - a.created_at);
// Update global cache with all events
updateGlobalCache(allEvents);
}
@ -1551,12 +1602,24 @@ @@ -1551,12 +1602,24 @@
<span class="toggle-label">Only show my events</span>
</label>
</div>
<button class="refresh-btn" on:click={() => {
const authors = showOnlyMyEvents && userPubkey ? [userPubkey] : null;
loadAllEvents(false, authors);
}} disabled={isLoadingEvents}>
🔄 Load More
</button>
<div class="events-view-buttons">
<button class="refresh-btn" on:click={() => {
const authors = showOnlyMyEvents && userPubkey ? [userPubkey] : null;
loadAllEvents(false, authors);
}} disabled={isLoadingEvents}>
🔄 Load More
</button>
<button class="reload-btn" on:click={() => {
const authors = showOnlyMyEvents && userPubkey ? [userPubkey] : null;
loadAllEvents(true, authors);
}} disabled={isLoadingEvents}>
{#if isLoadingEvents}
<div class="spinner"></div>
{:else}
🔄
{/if}
</button>
</div>
</div>
<div class="events-view-content" on:scroll={handleScroll}>
{#if filteredEvents.length > 0}
@ -1571,14 +1634,27 @@ @@ -1571,14 +1634,27 @@
{truncatePubkey(event.pubkey)}
</div>
<div class="events-view-kind">
<span class="kind-number">{event.kind}</span>
<span class="kind-number" class:delete-event={event.kind === 5}>{event.kind}</span>
<span class="kind-name">{getKindName(event.kind)}</span>
</div>
</div>
<div class="events-view-content">
{truncateContent(event.content)}
{#if event.kind === 5}
<div class="delete-event-info">
<span class="delete-event-label">🗑 Delete Event</span>
{#if event.tags && event.tags.length > 0}
<div class="delete-targets">
{#each event.tags.filter(tag => tag[0] === 'e') as eTag}
<span class="delete-target">Target: {eTag[1].slice(0, 8)}...{eTag[1].slice(-8)}</span>
{/each}
</div>
{/if}
</div>
{:else}
{truncateContent(event.content)}
{/if}
</div>
{#if (userRole === 'admin' || userRole === 'owner') || (userRole === 'write' && event.pubkey === userPubkey)}
{#if event.kind !== 5 && ((userRole === 'admin' || userRole === 'owner') || (userRole === 'write' && event.pubkey && event.pubkey === userPubkey))}
<button class="delete-btn" on:click|stopPropagation={() => deleteEvent(event.id)}>
🗑
</button>
@ -1777,7 +1853,7 @@ @@ -1777,7 +1853,7 @@
<div class="search-result-content">
{truncateContent(event.content)}
</div>
{#if (userRole === 'admin' || userRole === 'owner') || (userRole === 'write' && event.pubkey === userPubkey)}
{#if event.kind !== 5 && ((userRole === 'admin' || userRole === 'owner') || (userRole === 'write' && event.pubkey && event.pubkey === userPubkey))}
<button class="delete-btn" on:click|stopPropagation={() => deleteEvent(event.id)}>
🗑
</button>
@ -2801,7 +2877,13 @@ @@ -2801,7 +2877,13 @@
line-height: 1.5;
}
.export-btn, .import-btn, .refresh-btn {
.events-view-buttons {
display: flex;
gap: 0.5rem;
align-items: center;
}
.export-btn, .import-btn, .refresh-btn, .reload-btn {
padding: 0.5rem 1rem;
background: var(--primary);
color: white;
@ -2817,10 +2899,29 @@ @@ -2817,10 +2899,29 @@
height: 2em;
}
.export-btn:hover, .import-btn:hover, .refresh-btn:hover {
.export-btn:hover, .import-btn:hover, .refresh-btn:hover, .reload-btn:hover {
background: #00ACC1;
}
.reload-btn {
min-width: 2em;
justify-content: center;
}
.spinner {
width: 1em;
height: 1em;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.export-btn:disabled, .import-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
@ -3058,6 +3159,35 @@ @@ -3058,6 +3159,35 @@
color: white;
}
.kind-number.delete-event {
background: var(--warning);
}
.delete-event-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.delete-event-label {
font-weight: 500;
color: var(--warning);
}
.delete-targets {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.delete-target {
font-size: 0.75rem;
font-family: monospace;
color: var(--text-color);
opacity: 0.7;
}
.events-view-details {
border-top: 1px solid var(--border-color);
background: var(--header-bg);

39
app/web/src/nostr.js

@ -302,7 +302,7 @@ export async function fetchEvents(filters, options = {}) { @@ -302,7 +302,7 @@ export async function fetchEvents(filters, options = {}) {
}
}
// Fetch all events with timestamp-based pagination using NDK
// Fetch all events with timestamp-based pagination using NDK (including delete events)
export async function fetchAllEvents(options = {}) {
const {
limit = 100,
@ -317,6 +317,8 @@ export async function fetchAllEvents(options = {}) { @@ -317,6 +317,8 @@ export async function fetchAllEvents(options = {}) {
if (until) filters.until = until;
if (authors) filters.authors = authors;
// Don't specify kinds filter - this will include all events including delete events (kind 5)
const events = await fetchEvents(filters, {
limit: limit,
timeout: 30000
@ -409,6 +411,41 @@ export async function fetchEventById(eventId, options = {}) { @@ -409,6 +411,41 @@ export async function fetchEventById(eventId, options = {}) {
}
}
// Fetch delete events that target a specific event ID using Nostr
export async function fetchDeleteEventsByTarget(eventId, options = {}) {
const {
timeout = 10000
} = options;
console.log(`Fetching delete events for target: ${eventId}`);
try {
const ndk = nostrClient.getNDK();
const filters = {
kinds: [5], // Kind 5 is deletion
'#e': [eventId] // e-tag referencing the target event
};
console.log('Fetching delete events via NDK with filters:', filters);
// Use NDK's fetchEvents method
const events = await ndk.fetchEvents(filters, {
timeout
});
console.log(`Fetched ${events.size} delete events via NDK`);
// Convert NDK events to raw events
const rawEvents = Array.from(events).map(event => event.rawEvent());
return rawEvents;
} catch (error) {
console.error("Failed to fetch delete events via NDK:", error);
throw error;
}
}
// Initialize client connection
export async function initializeNostrClient() {

113
pkg/database/query-events.go

@ -37,6 +37,12 @@ func CheckExpiration(ev *event.E) (expired bool) { @@ -37,6 +37,12 @@ func CheckExpiration(ev *event.E) (expired bool) {
func (d *D) QueryEvents(c context.Context, f *filter.F) (
evs event.S, err error,
) {
return d.QueryEventsWithOptions(c, f, true)
}
func (d *D) QueryEventsWithOptions(c context.Context, f *filter.F, includeDeleteEvents bool) (
evs event.S, err error,
) {
// if there is Ids in the query, this overrides anything else
var expDeletes types.Uint40s
@ -195,16 +201,15 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -195,16 +201,15 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
// We don't need to do anything with direct event ID
// references as we will filter those out in the second pass
}
// Check for 'a' tags that reference parameterized replaceable
// events
// Check for 'a' tags that reference replaceable events
aTags := ev.Tags.GetAll([]byte("a"))
for _, aTag := range aTags {
if aTag.Len() < 2 {
continue
}
// Parse the 'a' tag value: kind:pubkey:d-tag
// Parse the 'a' tag value: kind:pubkey:d-tag (for parameterized) or kind:pubkey (for regular)
split := bytes.Split(aTag.Value(), []byte{':'})
if len(split) != 3 {
if len(split) < 2 {
continue
}
// Parse the kind
@ -214,8 +219,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -214,8 +219,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
continue
}
kk := kind.New(uint16(kindInt))
// Only process parameterized replaceable events
if !kind.IsParameterizedReplaceable(kk.K) {
// Process both regular and parameterized replaceable events
if !kind.IsReplaceable(kk.K) {
continue
}
// Parse the pubkey
@ -230,21 +235,30 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -230,21 +235,30 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
// Create the key for the deletion map using hex
// representation of pubkey
key := hex.Enc(pk) + ":" + strconv.Itoa(int(kk.K))
// Initialize the inner map if it doesn't exist
if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
deletionsByKindPubkeyDTag[key] = make(map[string]int64)
}
// Record the newest delete timestamp for this d-tag
dValue := string(split[2])
if ts, ok := deletionsByKindPubkeyDTag[key][dValue]; !ok || ev.CreatedAt > ts {
deletionsByKindPubkeyDTag[key][dValue] = ev.CreatedAt
if kind.IsParameterizedReplaceable(kk.K) {
// For parameterized replaceable events, use d-tag specific deletion
if len(split) < 3 {
continue
}
// Initialize the inner map if it doesn't exist
if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
deletionsByKindPubkeyDTag[key] = make(map[string]int64)
}
// Record the newest delete timestamp for this d-tag
dValue := string(split[2])
if ts, ok := deletionsByKindPubkeyDTag[key][dValue]; !ok || ev.CreatedAt > ts {
deletionsByKindPubkeyDTag[key][dValue] = ev.CreatedAt
}
} else {
// For regular replaceable events, mark as deleted by kind/pubkey
deletionsByKindPubkey[key] = true
}
// Debug logging
}
// For replaceable events, we need to check if there are any
// e-tags that reference events with the same kind and pubkey
for _, eTag := range eTags {
if eTag.Len() != 64 {
if len(eTag.Value()) != 64 {
continue
}
// Get the event ID from the e-tag
@ -252,15 +266,30 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -252,15 +266,30 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
if _, err = hex.DecBytes(evId, eTag.Value()); err != nil {
continue
}
// Query for the event
var targetEvs event.S
targetEvs, err = d.QueryEvents(
c, &filter.F{Ids: tag.NewFromBytesSlice(evId)},
)
if err != nil || len(targetEvs) == 0 {
continue
// Look for the target event in our current batch instead of querying
var targetEv *event.E
for _, candidateEv := range allEvents {
if utils.FastEqual(candidateEv.ID, evId) {
targetEv = candidateEv
break
}
}
// If not found in current batch, try to fetch it directly
if targetEv == nil {
// Get serial for the event ID
ser, serErr := d.GetSerialById(evId)
if serErr != nil || ser == nil {
continue
}
// Fetch the event by serial
targetEv, serErr = d.FetchEventBySerial(ser)
if serErr != nil || targetEv == nil {
continue
}
}
targetEv := targetEvs[0]
// Only allow users to delete their own events
if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
continue
@ -378,8 +407,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -378,8 +407,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
// )
}
// Skip events with kind 5 (Deletion)
if ev.Kind == kind.Deletion.K {
// Skip events with kind 5 (Deletion) unless explicitly requested
if ev.Kind == kind.Deletion.K && !includeDeleteEvents {
continue
}
// Check if this event's ID is in the filter
@ -408,16 +437,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -408,16 +437,8 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
// kind/pubkey and is not in the filter AND there isn't a newer
// event with the same kind/pubkey
if deletionsByKindPubkey[key] && !isIdInFilter {
// Check if there's a newer event with the same kind/pubkey
// that hasn't been specifically deleted
existing, exists := replaceableEvents[key]
if !exists || ev.CreatedAt > existing.CreatedAt {
// This is the newest event so far, keep it
replaceableEvents[key] = ev
} else {
// There's a newer event, skip this one
continue
}
// This replaceable event has been deleted, skip it
continue
} else {
// Normal replaceable event handling
existing, exists := replaceableEvents[key]
@ -501,3 +522,23 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) ( @@ -501,3 +522,23 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
}
return
}
// QueryDeleteEventsByTargetId queries for delete events that target a specific event ID
func (d *D) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (
evs event.S, err error,
) {
// Create a filter for deletion events with the target event ID in e-tags
f := &filter.F{
Kinds: kind.NewS(kind.Deletion),
Tags: tag.NewS(
tag.NewFromAny("#e", hex.Enc(targetEventId)),
),
}
// Query for the delete events
if evs, err = d.QueryEventsWithOptions(c, f, true); chk.E(err) {
return
}
return
}

Loading…
Cancel
Save