Compare commits

...

8 Commits

Author SHA1 Message Date
199f922208 Refactor deletion checks and error handling; bump version to v0.6.4. 2025-09-21 18:15:27 +01:00
405e223aa6 implement delete events 2025-09-21 18:06:11 +01:00
fc3a89a309 Remove unused JavaScript file index-tha189jf.js from dist directory.
- Cleaned up the `app/web/dist/` directory by deleting an unreferenced and outdated build artifact.
- Maintained a lean and organized repository structure.
2025-09-21 17:17:31 +01:00
ba8166da07 Remove unused JavaScript file index-wnwvj11w.js from dist directory.
- Cleaned up the `app/web/dist/` directory by deleting an unreferenced and outdated build artifact.
- Maintained a lean and organized repository structure.
2025-09-21 17:17:15 +01:00
3e3af08644 Remove unused JavaScript file index-wnwvj11w.js from dist directory.
- Cleaned up the `app/web/dist/` directory by deleting an unreferenced and outdated build artifact.
- Maintained a lean and organized repository structure.
2025-09-21 16:39:45 +01:00
fbdf565bf7 Remove unused JavaScript file index-sskmjaqz.js from dist directory.
- Cleaned up the `app/web/dist/` directory by deleting an unreferenced and outdated build artifact.
- Maintained a lean and organized repository structure.
2025-09-21 16:33:23 +01:00
14b6960070 Add admin-only "All Events Log" feature with WebSocket integration.
- Implemented an "All Events Log" section accessible only to admin users.
- Added WebSocket-based data fetching to retrieve all events from the relay.
- Included profile caching and metadata fetching for event authors.
- Updated UI components to display events with expandable raw JSON details.
- Adjusted CSS for avatar sizes and improved layout.
- Refactored logout logic to reset all event states.
2025-09-21 16:31:06 +01:00
f9896e52ea use websockets for events log 2025-09-21 16:12:10 +01:00
11 changed files with 1693 additions and 318 deletions

7
.idea/jsLibraryMappings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$/../github.com/jumble" libraries="{jumble/node_modules}" />
<file url="file://$PROJECT_DIR$/../github.com/mleku/jumble" libraries="{jumble/node_modules}" />
</component>
</project>

View File

@@ -145,12 +145,10 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
if ev, err = l.FetchEventBySerial(s); chk.E(err) { if ev, err = l.FetchEventBySerial(s); chk.E(err) {
continue continue
} }
// check that the author is the same as the signer of the // allow deletion if the signer is the author OR an admin/owner
// delete, for the e tag case the author is the signer of if !(ownerDelete || utils.FastEqual(env.E.Pubkey, ev.Pubkey)) {
// the event.
if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
log.W.F( log.W.F(
"HandleDelete: attempted deletion of event %s by different user - delete pubkey=%s, event pubkey=%s", "HandleDelete: attempted deletion of event %s by unauthorized user - delete pubkey=%s, event pubkey=%s",
hex.Enc(ev.ID), hex.Enc(env.E.Pubkey), hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
hex.Enc(ev.Pubkey), hex.Enc(ev.Pubkey),
) )

View File

@@ -18,7 +18,9 @@ import (
"next.orly.dev/pkg/acl" "next.orly.dev/pkg/acl"
"next.orly.dev/pkg/database" "next.orly.dev/pkg/database"
"next.orly.dev/pkg/encoders/event" "next.orly.dev/pkg/encoders/event"
"next.orly.dev/pkg/encoders/filter"
"next.orly.dev/pkg/encoders/hex" "next.orly.dev/pkg/encoders/hex"
"next.orly.dev/pkg/encoders/tag"
"next.orly.dev/pkg/protocol/auth" "next.orly.dev/pkg/protocol/auth"
"next.orly.dev/pkg/protocol/publish" "next.orly.dev/pkg/protocol/publish"
) )
@@ -160,6 +162,8 @@ func (s *Server) UserInterface() {
// Export endpoints // Export endpoints
s.mux.HandleFunc("/api/export", s.handleExport) s.mux.HandleFunc("/api/export", s.handleExport)
s.mux.HandleFunc("/api/export/mine", s.handleExportMine) s.mux.HandleFunc("/api/export/mine", s.handleExportMine)
// Events endpoints
s.mux.HandleFunc("/api/events/mine", s.handleEventsMine)
// Import endpoint (admin only) // Import endpoint (admin only)
s.mux.HandleFunc("/api/import", s.handleImport) s.mux.HandleFunc("/api/import", s.handleImport)
} }
@@ -478,10 +482,121 @@ func (s *Server) handleImport(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Empty request body", http.StatusBadRequest) http.Error(w, "Empty request body", http.StatusBadRequest)
return return
} }
s.D.Import(r.Body) s.D.Import(r.Body)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
w.Write([]byte(`{"success": true, "message": "Import started"}`)) w.Write([]byte(`{"success": true, "message": "Import started"}`))
} }
// handleEventsMine returns the authenticated user's events in JSON format with pagination
func (s *Server) handleEventsMine(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Require auth cookie
c, err := r.Cookie("orly_auth")
if err != nil || c.Value == "" {
http.Error(w, "Not authenticated", http.StatusUnauthorized)
return
}
pubkey, err := hex.Dec(c.Value)
if chk.E(err) {
http.Error(w, "Invalid auth cookie", http.StatusUnauthorized)
return
}
// Parse pagination parameters
query := r.URL.Query()
limit := 50 // default limit
if l := query.Get("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
limit = parsed
}
}
offset := 0
if o := query.Get("offset"); o != "" {
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
offset = parsed
}
}
// Use QueryEvents with filter for this user's events
f := &filter.F{
Authors: tag.NewFromBytesSlice(pubkey),
}
log.Printf("DEBUG: Querying events for pubkey: %s", hex.Enc(pubkey))
events, err := s.D.QueryEvents(s.Ctx, f)
if chk.E(err) {
log.Printf("DEBUG: QueryEvents failed: %v", err)
http.Error(w, "Failed to query events", http.StatusInternalServerError)
return
}
log.Printf("DEBUG: QueryEvents returned %d events", len(events))
// If no events found, let's also check if there are any events at all in the database
if len(events) == 0 {
// Create a filter to get any events (no authors filter)
allEventsFilter := &filter.F{}
allEvents, err := s.D.QueryEvents(s.Ctx, allEventsFilter)
if err == nil {
log.Printf("DEBUG: Total events in database: %d", len(allEvents))
} else {
log.Printf("DEBUG: Failed to query all events: %v", err)
}
}
// Events are already sorted by QueryEvents in reverse chronological order
// Apply offset and limit manually since QueryEvents doesn't support offset
totalEvents := len(events)
start := offset
if start > totalEvents {
start = totalEvents
}
end := start + limit
if end > totalEvents {
end = totalEvents
}
paginatedEvents := events[start:end]
// Convert events to JSON response format
type EventResponse struct {
ID string `json:"id"`
Kind int `json:"kind"`
CreatedAt int64 `json:"created_at"`
Content string `json:"content"`
RawJSON string `json:"raw_json"`
}
response := struct {
Events []EventResponse `json:"events"`
Total int `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}{
Events: make([]EventResponse, len(paginatedEvents)),
Total: totalEvents,
Offset: offset,
Limit: limit,
}
for i, ev := range paginatedEvents {
response.Events[i] = EventResponse{
ID: hex.Enc(ev.ID),
Kind: int(ev.Kind),
CreatedAt: int64(ev.CreatedAt),
Content: string(ev.Content),
RawJSON: string(ev.Serialize()),
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

160
app/web/dist/index-w8zpqk4w.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nostr Relay</title> <title>Nostr Relay</title>
<link rel="stylesheet" crossorigin href="./index-cepjm5g7.css"><script type="module" crossorigin src="./index-bnzmmj1a.js"></script></head> <link rel="stylesheet" crossorigin href="./index-q4cwd1fy.css"><script type="module" crossorigin src="./index-w8zpqk4w.js"></script></head>
<body> <body>
<script> <script>
// Apply system theme preference immediately to avoid flash of wrong theme // Apply system theme preference immediately to avoid flash of wrong theme

File diff suppressed because it is too large Load Diff

View File

@@ -130,8 +130,8 @@ button:hover {
} }
.user-avatar { .user-avatar {
width: 40px; width: 2em;
height: 40px; height: 2em;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
border: 2px solid white; border: 2px solid white;

View File

@@ -173,10 +173,10 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
} }
} }
if ev.CreatedAt < maxTs { if ev.CreatedAt < maxTs {
// err = fmt.Errorf( err = errorf.E(
// "blocked: was deleted by address %s: event is older than the delete: event: %d delete: %d", "blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
// at, ev.CreatedAt, maxTs, ev.ID, at, ev.CreatedAt, maxTs,
// ) )
return return
} }
return return
@@ -203,22 +203,14 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
return return
} }
if len(s) > 0 { if len(s) > 0 {
// For e-tag deletions (delete by ID), any deletion event means the event cannot be resubmitted // Any e-tag deletion found means the exact event was deleted and cannot be resubmitted
// regardless of timestamp, since it's a specific deletion of this exact event err = errorf.E("blocked: %0x has been deleted", ev.ID)
// err = errorf.E(
// "blocked: was deleted by ID and cannot be resubmitted",
// // ev.ID,
// )
return return
} }
} }
if len(sers) > 0 { if len(sers) > 0 {
// For e-tag deletions (delete by ID), any deletion event means the event cannot be resubmitted // Any e-tag deletion found means the exact event was deleted and cannot be resubmitted
// regardless of timestamp, since it's a specific deletion of this exact event err = errorf.E("blocked: %0x has been deleted", ev.ID)
// err = errorf.E(
// "blocked: was deleted by ID and cannot be resubmitted",
// // ev.ID,
// )
return return
} }

View File

@@ -1 +1 @@
v0.6.1 v0.6.4