Browse Source

Enhance delete event handling and logging

- Improved logging for delete events in handle-delete.go, including detailed information about the event and its tags.
- Added checks for admin and owner deletions, with appropriate logging for each case.
- Updated HandleEvent to process delete events more robustly, including success and error logging.
- Introduced a new fetchEventById function in nostr.js to verify event deletion.
- Updated App.svelte to handle event deletion verification and state management.
- Changed favicon references in HTML files to use the new orly-favicon.png.
- Added orly-favicon.png to the public and docs directories for consistent branding.
main
mleku 3 months ago
parent
commit
67a74980f9
No known key found for this signature in database
  1. 51
      app/handle-delete.go
  2. 59
      app/handle-event.go
  3. 25
      app/main.go
  4. 11
      app/server.go
  5. 2
      app/web/dist/index.html
  6. 2
      app/web/public/index.html
  7. BIN
      app/web/public/orly-favicon.png
  8. 85
      app/web/src/App.svelte
  9. 2
      app/web/src/constants.js
  10. 53
      app/web/src/nostr.js
  11. BIN
      docs/orly-favicon.png
  12. 20
      pkg/database/delete-event.go

51
app/handle-delete.go

@ -24,23 +24,36 @@ func (l *Listener) GetSerialsFromFilter(f *filter.F) (
} }
func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
// log.I.C( log.I.F("HandleDelete: processing delete event %0x from pubkey %0x", env.E.ID, env.E.Pubkey)
// func() string { log.I.F("HandleDelete: delete event tags: %d tags", len(*env.E.Tags))
// return fmt.Sprintf( for i, t := range *env.E.Tags {
// "delete event\n%s", env.E.Serialize(), log.I.F("HandleDelete: tag %d: %s = %s", i, string(t.Key()), string(t.Value()))
// ) }
// },
// )
var ownerDelete bool var ownerDelete bool
for _, pk := range l.Admins { for _, pk := range l.Admins {
if utils.FastEqual(pk, env.E.Pubkey) { if utils.FastEqual(pk, env.E.Pubkey) {
ownerDelete = true ownerDelete = true
log.I.F("HandleDelete: delete event from admin/owner %0x", env.E.Pubkey)
break break
} }
} }
if !ownerDelete {
for _, pk := range l.Owners {
if utils.FastEqual(pk, env.E.Pubkey) {
ownerDelete = true
log.I.F("HandleDelete: delete event from owner %0x", env.E.Pubkey)
break
}
}
}
if !ownerDelete {
log.I.F("HandleDelete: delete event from regular user %0x", env.E.Pubkey)
}
// process the tags in the delete event // process the tags in the delete event
var deleteErr error var deleteErr error
var validDeletionFound bool var validDeletionFound bool
var deletionCount int
for _, t := range *env.E.Tags { for _, t := range *env.E.Tags {
// first search for a tags, as these are the simplest to process // first search for a tags, as these are the simplest to process
if utils.FastEqual(t.Key(), []byte("a")) { if utils.FastEqual(t.Key(), []byte("a")) {
@ -109,8 +122,10 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
if err = l.DeleteEventBySerial( if err = l.DeleteEventBySerial(
l.Ctx(), s, ev, l.Ctx(), s, ev,
); chk.E(err) { ); chk.E(err) {
log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
continue continue
} }
deletionCount++
} }
} }
} }
@ -121,21 +136,27 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
if utils.FastEqual(t.Key(), []byte("e")) { if utils.FastEqual(t.Key(), []byte("e")) {
val := t.Value() val := t.Value()
if len(val) == 0 { if len(val) == 0 {
log.W.F("HandleDelete: empty e-tag value")
continue continue
} }
log.I.F("HandleDelete: processing e-tag with value: %s", string(val))
var dst []byte var dst []byte
if b, e := hex.Dec(string(val)); chk.E(e) { if b, e := hex.Dec(string(val)); chk.E(e) {
log.E.F("HandleDelete: failed to decode hex event ID %s: %v", string(val), e)
continue continue
} else { } else {
dst = b dst = b
log.I.F("HandleDelete: decoded event ID: %0x", dst)
} }
f := &filter.F{ f := &filter.F{
Ids: tag.NewFromBytesSlice(dst), Ids: tag.NewFromBytesSlice(dst),
} }
var sers types.Uint40s var sers types.Uint40s
if sers, err = l.GetSerialsFromFilter(f); chk.E(err) { if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
log.E.F("HandleDelete: failed to get serials from filter: %v", err)
continue continue
} }
log.I.F("HandleDelete: found %d serials for event ID %s", len(sers), string(val))
// if found, delete them // if found, delete them
if len(sers) > 0 { if len(sers) > 0 {
// there should be only one event per serial, so we can just // there should be only one event per serial, so we can just
@ -164,8 +185,10 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
hex.Enc(ev.ID), hex.Enc(env.E.Pubkey), hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
) )
if err = l.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) { if err = l.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) {
log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
continue continue
} }
deletionCount++
} }
continue continue
} }
@ -204,17 +227,27 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) { if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
continue continue
} }
validDeletionFound = true
log.I.F(
"HandleDelete: deleting event %s via k-tag by authorized user %s",
hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
)
if err = l.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) {
log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
continue
}
deletionCount++
} }
continue
} }
} }
continue
} }
// If no valid deletions were found, return an error // If no valid deletions were found, return an error
if !validDeletionFound { if !validDeletionFound {
log.W.F("HandleDelete: no valid deletions found for event %0x", env.E.ID)
return fmt.Errorf("blocked: cannot delete events that belong to other users") return fmt.Errorf("blocked: cannot delete events that belong to other users")
} }
log.I.F("HandleDelete: successfully processed %d deletions for event %0x", deletionCount, env.E.ID)
return return
} }

59
app/handle-event.go

@ -167,6 +167,21 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
// user has write access or better, continue // user has write access or better, continue
// log.D.F("user has %s access", accessLevel) // log.D.F("user has %s access", accessLevel)
} }
// check if event is ephemeral - if so, deliver and return early
if kind.IsEphemeral(env.E.Kind) {
log.D.F("handling ephemeral event %0x (kind %d)", env.E.ID, env.E.Kind)
// Send OK response for ephemeral events
if err = Ok.Ok(l, env, ""); chk.E(err) {
return
}
// Deliver the event to subscribers immediately
clonedEvent := env.E.Clone()
go l.publishers.Deliver(clonedEvent)
log.D.F("delivered ephemeral event %0x", env.E.ID)
return
}
// check for protected tag (NIP-70) // check for protected tag (NIP-70)
protectedTag := env.E.Tags.GetFirst([]byte("-")) protectedTag := env.E.Tags.GetFirst([]byte("-"))
if protectedTag != nil && acl.Registry.Active.Load() != "none" { if protectedTag != nil && acl.Registry.Active.Load() != "none" {
@ -183,7 +198,9 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
} }
// if the event is a delete, process the delete // if the event is a delete, process the delete
if env.E.Kind == kind.EventDeletion.K { if env.E.Kind == kind.EventDeletion.K {
log.I.F("processing delete event %0x", env.E.ID)
if err = l.HandleDelete(env); err != nil { 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:") { if strings.HasPrefix(err.Error(), "blocked:") {
errStr := err.Error()[len("blocked: "):len(err.Error())] errStr := err.Error()[len("blocked: "):len(err.Error())]
if err = Ok.Error( if err = Ok.Error(
@ -193,10 +210,41 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
} }
return 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) {
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 {
if strings.HasPrefix(err.Error(), "blocked:") {
errStr := err.Error()[len("blocked: "):len(err.Error())]
if err = Ok.Error(
l, env, errStr,
); chk.E(err) {
return
}
return
}
chk.E(err)
return
}
// Deliver the delete event to subscribers
clonedEvent := env.E.Clone()
go l.publishers.Deliver(clonedEvent)
log.D.F("processed delete event %0x", env.E.ID)
return
} else { } else {
// check if the event was deleted // check if the event was deleted
if err = l.CheckForDeleted(env.E, l.Admins); err != nil { // Combine admins and owners for deletion checking
adminOwners := append(l.Admins, l.Owners...)
if err = l.CheckForDeleted(env.E, adminOwners); err != nil {
if strings.HasPrefix(err.Error(), "blocked:") { if strings.HasPrefix(err.Error(), "blocked:") {
errStr := err.Error()[len("blocked: "):len(err.Error())] errStr := err.Error()[len("blocked: "):len(err.Error())]
if err = Ok.Error( if err = Ok.Error(
@ -234,12 +282,21 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
go l.publishers.Deliver(clonedEvent) go l.publishers.Deliver(clonedEvent)
log.D.F("saved event %0x", env.E.ID) log.D.F("saved event %0x", env.E.ID)
var isNewFromAdmin bool var isNewFromAdmin bool
// Check if event is from admin or owner
for _, admin := range l.Admins { for _, admin := range l.Admins {
if utils.FastEqual(admin, env.E.Pubkey) { if utils.FastEqual(admin, env.E.Pubkey) {
isNewFromAdmin = true isNewFromAdmin = true
break break
} }
} }
if !isNewFromAdmin {
for _, owner := range l.Owners {
if utils.FastEqual(owner, env.E.Pubkey) {
isNewFromAdmin = true
break
}
}
}
if isNewFromAdmin { if isNewFromAdmin {
log.I.F("new event from admin %0x", env.E.Pubkey) log.I.F("new event from admin %0x", env.E.Pubkey)
// if a follow list was saved, reconfigure ACLs now that it is persisted // if a follow list was saved, reconfigure ACLs now that it is persisted

25
app/main.go

@ -36,6 +36,18 @@ func Run(
} }
adminKeys = append(adminKeys, pk) adminKeys = append(adminKeys, pk)
} }
// get the owners
var ownerKeys [][]byte
for _, owner := range cfg.Owners {
if len(owner) == 0 {
continue
}
var pk []byte
if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(owner); chk.E(err) {
continue
}
ownerKeys = append(ownerKeys, pk)
}
// start listener // start listener
l := &Server{ l := &Server{
Ctx: ctx, Ctx: ctx,
@ -43,6 +55,7 @@ func Run(
D: db, D: db,
publishers: publish.New(NewPublisher(ctx)), publishers: publish.New(NewPublisher(ctx)),
Admins: adminKeys, Admins: adminKeys,
Owners: ownerKeys,
} }
// Initialize sprocket manager // Initialize sprocket manager
@ -68,6 +81,18 @@ func Run(
cfg.Admins = append(cfg.Admins, pk) cfg.Admins = append(cfg.Admins, pk)
log.I.F("added relay identity to admins for follow-list whitelisting") log.I.F("added relay identity to admins for follow-list whitelisting")
} }
// also ensure relay identity pubkey is considered an owner for full control
found = false
for _, o := range cfg.Owners {
if o == pk {
found = true
break
}
}
if !found {
cfg.Owners = append(cfg.Owners, pk)
log.I.F("added relay identity to owners for full control")
}
} }
} }

11
app/server.go

@ -34,6 +34,7 @@ type Server struct {
remote string remote string
publishers *publish.S publishers *publish.S
Admins [][]byte Admins [][]byte
Owners [][]byte
*database.D *database.D
// optional reverse proxy for dev web server // optional reverse proxy for dev web server
@ -179,7 +180,7 @@ func (s *Server) UserInterface() {
s.challengeMutex.Unlock() s.challengeMutex.Unlock()
} }
// Serve favicon.ico by serving orly.png // Serve favicon.ico by serving orly-favicon.png
s.mux.HandleFunc("/favicon.ico", s.handleFavicon) s.mux.HandleFunc("/favicon.ico", s.handleFavicon)
// Serve the main login interface (and static assets) or proxy in dev mode // Serve the main login interface (and static assets) or proxy in dev mode
@ -206,7 +207,7 @@ func (s *Server) UserInterface() {
s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig) s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig)
} }
// handleFavicon serves orly.png as favicon.ico // handleFavicon serves orly-favicon.png as favicon.ico
func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) { func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) {
// In dev mode with proxy configured, forward to dev server // In dev mode with proxy configured, forward to dev server
if s.devProxy != nil { if s.devProxy != nil {
@ -214,14 +215,14 @@ func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) {
return return
} }
// Serve orly.png as favicon.ico from embedded web app // Serve orly-favicon.png as favicon.ico from embedded web app
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day
// Create a request for orly.png and serve it // Create a request for orly-favicon.png and serve it
faviconReq := &http.Request{ faviconReq := &http.Request{
Method: "GET", Method: "GET",
URL: &url.URL{Path: "/orly.png"}, URL: &url.URL{Path: "/orly-favicon.png"},
} }
ServeEmbeddedWeb(w, faviconReq) ServeEmbeddedWeb(w, faviconReq)
} }

2
app/web/dist/index.html vendored

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Next Orly</title> <title>Next Orly</title>
<link rel="icon" href="/orly.png" type="image/png" /> <link rel="icon" href="/orly-favicon.png" type="image/png" />
<link rel="stylesheet" href="/bundle.css" /> <link rel="stylesheet" href="/bundle.css" />
</head> </head>
<body> <body>

2
app/web/public/index.html

@ -6,7 +6,7 @@
<title>ORLY?</title> <title>ORLY?</title>
<link rel="icon" type="image/png" href="/orly.png" /> <link rel="icon" type="image/png" href="/orly-favicon.png" />
<link rel="stylesheet" href="/global.css" /> <link rel="stylesheet" href="/global.css" />
<link rel="stylesheet" href="/build/bundle.css" /> <link rel="stylesheet" href="/build/bundle.css" />

BIN
app/web/public/orly-favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

85
app/web/src/App.svelte

@ -1,6 +1,6 @@
<script> <script>
import LoginModal from './LoginModal.svelte'; import LoginModal from './LoginModal.svelte';
import { initializeNostrClient, fetchUserProfile, fetchAllEvents, fetchUserEvents, searchEvents, nostrClient, NostrClient } from './nostr.js'; import { initializeNostrClient, fetchUserProfile, fetchAllEvents, fetchUserEvents, searchEvents, fetchEventById, nostrClient, NostrClient } from './nostr.js';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
let isDarkTheme = false; let isDarkTheme = false;
@ -40,6 +40,13 @@
// Events filter toggle // Events filter toggle
let showOnlyMyEvents = false; let showOnlyMyEvents = false;
// My Events state
let myEvents = [];
let isLoadingMyEvents = false;
let hasMoreMyEvents = true;
let oldestMyEventTimestamp = null;
let newestMyEventTimestamp = null;
// Sprocket management state // Sprocket management state
let sprocketScript = ''; let sprocketScript = '';
@ -232,8 +239,40 @@
console.log('Delete event published:', result); console.log('Delete event published:', result);
if (result.success && result.okCount > 0) { if (result.success && result.okCount > 0) {
// Remove from local list // Wait a moment for the deletion to propagate
await new Promise(resolve => setTimeout(resolve, 2000));
// Verify the event was actually deleted by trying to fetch it
try {
const deletedEvent = await fetchEventById(eventId, { timeout: 5000 });
if (deletedEvent) {
console.warn('Event still exists after deletion attempt:', deletedEvent);
alert(`Warning: Delete event was accepted by ${result.okCount} relay(s), but the event still exists on the relay. This may indicate the relay does not properly handle delete events.`);
} else {
console.log('Event successfully deleted and verified');
}
} catch (fetchError) {
console.log('Could not fetch event after deletion (likely deleted):', fetchError.message);
}
// Remove from local lists
allEvents = allEvents.filter(event => event.id !== eventId); allEvents = allEvents.filter(event => event.id !== eventId);
myEvents = myEvents.filter(event => event.id !== eventId);
// Remove from global cache
globalEventsCache = globalEventsCache.filter(event => event.id !== eventId);
// Remove from search results cache
for (const [tabId, searchResult] of searchResults) {
if (searchResult.events) {
searchResult.events = searchResult.events.filter(event => event.id !== eventId);
searchResults.set(tabId, searchResult);
}
}
// Update persistent state
savePersistentState();
alert(`Event deleted successfully (accepted by ${result.okCount} relay(s))`); alert(`Event deleted successfully (accepted by ${result.okCount} relay(s))`);
} else { } else {
throw new Error('No relays accepted the delete event'); throw new Error('No relays accepted the delete event');
@ -241,7 +280,7 @@
} else { } else {
// Admin/owner deleting someone else's event - only publish to local relay // Admin/owner deleting someone else's event - only publish to local relay
// We need to publish only to the local relay, not external ones // We need to publish only to the local relay, not external ones
const localRelayUrl = `wss://${window.location.host}/ws`; const localRelayUrl = `wss://${window.location.host}/`;
// Create a modified client that only connects to the local relay // Create a modified client that only connects to the local relay
const localClient = new NostrClient(); const localClient = new NostrClient();
@ -251,8 +290,40 @@
console.log('Delete event published to local relay only:', result); console.log('Delete event published to local relay only:', result);
if (result.success && result.okCount > 0) { if (result.success && result.okCount > 0) {
// Remove from local list // Wait a moment for the deletion to propagate
await new Promise(resolve => setTimeout(resolve, 2000));
// Verify the event was actually deleted by trying to fetch it
try {
const deletedEvent = await fetchEventById(eventId, { timeout: 5000 });
if (deletedEvent) {
console.warn('Event still exists after deletion attempt:', deletedEvent);
alert(`Warning: Delete event was accepted by ${result.okCount} relay(s), but the event still exists on the relay. This may indicate the relay does not properly handle delete events.`);
} else {
console.log('Event successfully deleted and verified');
}
} catch (fetchError) {
console.log('Could not fetch event after deletion (likely deleted):', fetchError.message);
}
// Remove from local lists
allEvents = allEvents.filter(event => event.id !== eventId); allEvents = allEvents.filter(event => event.id !== eventId);
myEvents = myEvents.filter(event => event.id !== eventId);
// Remove from global cache
globalEventsCache = globalEventsCache.filter(event => event.id !== eventId);
// Remove from search results cache
for (const [tabId, searchResult] of searchResults) {
if (searchResult.events) {
searchResult.events = searchResult.events.filter(event => event.id !== eventId);
searchResults.set(tabId, searchResult);
}
}
// Update persistent state
savePersistentState();
alert(`Event deleted successfully (local relay only - admin/owner deleting other user's event)`); alert(`Event deleted successfully (local relay only - admin/owner deleting other user's event)`);
} else { } else {
throw new Error('Local relay did not accept the delete event'); throw new Error('Local relay did not accept the delete event');
@ -1102,12 +1173,8 @@
if (reset) { if (reset) {
myEvents = events; myEvents = events;
// Update cache
updateCache(userPubkey, events);
} else { } else {
myEvents = [...myEvents, ...events]; myEvents = [...myEvents, ...events];
// Update cache with all events
updateCache(userPubkey, myEvents);
} }
// Update oldest timestamp for next pagination // Update oldest timestamp for next pagination
@ -1355,7 +1422,7 @@
<!-- Header --> <!-- Header -->
<header class="main-header" class:dark-theme={isDarkTheme}> <header class="main-header" class:dark-theme={isDarkTheme}>
<div class="header-content"> <div class="header-content">
<img src="/orly.png" alt="Orly Logo" class="logo"/> <img src="/orly-favicon.png" alt="Orly Logo" class="logo"/>
{#if isSearchMode} {#if isSearchMode}
<div class="search-input-container"> <div class="search-input-container">
<input <input

2
app/web/src/constants.js

@ -1,5 +1,5 @@
// Default Nostr relays for searching // Default Nostr relays for searching
export const DEFAULT_RELAYS = [ export const DEFAULT_RELAYS = [
// Use the local relay WebSocket endpoint // Use the local relay WebSocket endpoint
`wss://${window.location.host}/ws`, `wss://${window.location.host}/`,
]; ];

53
app/web/src/nostr.js

@ -1,4 +1,4 @@
import NDK, { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import NDK, { NDKPrivateKeySigner, NDKEvent } from '@nostr-dev-kit/ndk';
import { DEFAULT_RELAYS } from "./constants.js"; import { DEFAULT_RELAYS } from "./constants.js";
// NDK-based Nostr client wrapper // NDK-based Nostr client wrapper
@ -30,7 +30,13 @@ class NostrClient {
console.log(`Adding relay to NDK: ${relayUrl}`); console.log(`Adding relay to NDK: ${relayUrl}`);
try { try {
await this.ndk.addRelay(relayUrl); // For now, just update the DEFAULT_RELAYS array and reconnect
// This is a simpler approach that avoids replacing the NDK instance
DEFAULT_RELAYS.push(relayUrl);
// Reconnect with the updated relay list
await this.connect();
console.log(`✓ Successfully added relay ${relayUrl}`); console.log(`✓ Successfully added relay ${relayUrl}`);
return true; return true;
} catch (error) { } catch (error) {
@ -68,7 +74,10 @@ class NostrClient {
disconnect() { disconnect() {
console.log("Disconnecting NDK"); console.log("Disconnecting NDK");
this.ndk.destroy(); // Note: NDK doesn't have a destroy method, just disconnect
if (this.ndk && typeof this.ndk.disconnect === 'function') {
this.ndk.disconnect();
}
this.isConnected = false; this.isConnected = false;
} }
@ -77,7 +86,7 @@ class NostrClient {
console.log("Publishing event via NDK:", event); console.log("Publishing event via NDK:", event);
try { try {
const ndkEvent = this.ndk.createEvent(event); const ndkEvent = new NDKEvent(this.ndk, event);
await ndkEvent.publish(); await ndkEvent.publish();
console.log("✓ Event published successfully via NDK"); console.log("✓ Event published successfully via NDK");
return { success: true, okCount: 1, errorCount: 0 }; return { success: true, okCount: 1, errorCount: 0 };
@ -364,6 +373,42 @@ export async function searchEvents(searchQuery, options = {}) {
return events; return events;
} }
// Fetch a specific event by ID
export async function fetchEventById(eventId, options = {}) {
const {
timeout = 10000,
relays = null
} = options;
console.log(`Fetching event by ID: ${eventId}`);
try {
const ndk = nostrClient.getNDK();
const filters = {
ids: [eventId]
};
console.log('Fetching event via NDK with filters:', filters);
// Use NDK's fetchEvents method
const events = await ndk.fetchEvents(filters, {
timeout
});
console.log(`Fetched ${events.size} events via NDK`);
// Convert NDK events to raw events
const rawEvents = Array.from(events).map(event => event.rawEvent());
// Return the first event if found, null otherwise
return rawEvents.length > 0 ? rawEvents[0] : null;
} catch (error) {
console.error("Failed to fetch event by ID via NDK:", error);
throw error;
}
}
// Initialize client connection // Initialize client connection
export async function initializeNostrClient() { export async function initializeNostrClient() {

BIN
docs/orly-favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

20
pkg/database/delete-event.go

@ -45,32 +45,50 @@ func (d *D) DeleteEvent(c context.Context, eid []byte) (err error) {
func (d *D) DeleteEventBySerial( func (d *D) DeleteEventBySerial(
c context.Context, ser *types.Uint40, ev *event.E, c context.Context, ser *types.Uint40, ev *event.E,
) (err error) { ) (err error) {
d.Logger.Infof("DeleteEventBySerial: deleting event %0x (serial %d)", ev.ID, ser.Get())
// Get all indexes for the event // Get all indexes for the event
var idxs [][]byte var idxs [][]byte
idxs, err = GetIndexesForEvent(ev, ser.Get()) idxs, err = GetIndexesForEvent(ev, ser.Get())
if chk.E(err) { if chk.E(err) {
d.Logger.Errorf("DeleteEventBySerial: failed to get indexes for event %0x: %v", ev.ID, err)
return return
} }
d.Logger.Infof("DeleteEventBySerial: found %d indexes for event %0x", len(idxs), ev.ID)
// Get the event key // Get the event key
eventKey := new(bytes.Buffer) eventKey := new(bytes.Buffer)
if err = indexes.EventEnc(ser).MarshalWrite(eventKey); chk.E(err) { if err = indexes.EventEnc(ser).MarshalWrite(eventKey); chk.E(err) {
d.Logger.Errorf("DeleteEventBySerial: failed to create event key for %0x: %v", ev.ID, err)
return return
} }
// Delete the event and all its indexes in a transaction // Delete the event and all its indexes in a transaction
err = d.Update( err = d.Update(
func(txn *badger.Txn) (err error) { func(txn *badger.Txn) (err error) {
// Delete the event // Delete the event
if err = txn.Delete(eventKey.Bytes()); chk.E(err) { if err = txn.Delete(eventKey.Bytes()); chk.E(err) {
d.Logger.Errorf("DeleteEventBySerial: failed to delete event %0x: %v", ev.ID, err)
return return
} }
d.Logger.Infof("DeleteEventBySerial: deleted event %0x", ev.ID)
// Delete all indexes // Delete all indexes
for _, key := range idxs { for i, key := range idxs {
if err = txn.Delete(key); chk.E(err) { if err = txn.Delete(key); chk.E(err) {
d.Logger.Errorf("DeleteEventBySerial: failed to delete index %d for event %0x: %v", i, ev.ID, err)
return return
} }
} }
d.Logger.Infof("DeleteEventBySerial: deleted %d indexes for event %0x", len(idxs), ev.ID)
return return
}, },
) )
if chk.E(err) {
d.Logger.Errorf("DeleteEventBySerial: transaction failed for event %0x: %v", ev.ID, err)
return
}
d.Logger.Infof("DeleteEventBySerial: successfully deleted event %0x and all indexes", ev.ID)
return return
} }

Loading…
Cancel
Save