forked from mleku/next.orly.dev
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
199f922208
|
|||
|
405e223aa6
|
|||
|
fc3a89a309
|
|||
|
ba8166da07
|
|||
|
3e3af08644
|
|||
|
fbdf565bf7
|
|||
|
14b6960070
|
|||
|
f9896e52ea
|
7
.idea/jsLibraryMappings.xml
generated
Normal file
7
.idea/jsLibraryMappings.xml
generated
Normal 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>
|
||||||
@@ -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),
|
||||||
)
|
)
|
||||||
|
|||||||
117
app/server.go
117
app/server.go
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
160
app/web/dist/index-bnzmmj1a.js
vendored
160
app/web/dist/index-bnzmmj1a.js
vendored
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
160
app/web/dist/index-w8zpqk4w.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
app/web/dist/index.html
vendored
2
app/web/dist/index.html
vendored
@@ -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
|
||||||
|
|||||||
1525
app/web/src/App.jsx
1525
app/web/src/App.jsx
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.6.1
|
v0.6.4
|
||||||
Reference in New Issue
Block a user