You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
5.9 KiB
262 lines
5.9 KiB
package dgraph |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"fmt" |
|
"time" |
|
|
|
"github.com/dgraph-io/dgo/v230/protos/api" |
|
"next.orly.dev/pkg/database/indexes/types" |
|
"git.mleku.dev/mleku/nostr/encoders/event" |
|
"git.mleku.dev/mleku/nostr/encoders/hex" |
|
) |
|
|
|
// DeleteEvent deletes an event by its ID |
|
func (d *D) DeleteEvent(c context.Context, eid []byte) error { |
|
idStr := hex.Enc(eid) |
|
|
|
// Find the event's UID |
|
query := fmt.Sprintf(`{ |
|
event(func: eq(event.id, %q)) { |
|
uid |
|
} |
|
}`, idStr) |
|
|
|
resp, err := d.Query(c, query) |
|
if err != nil { |
|
return fmt.Errorf("failed to find event for deletion: %w", err) |
|
} |
|
|
|
// Parse UID |
|
var result struct { |
|
Event []struct { |
|
UID string `json:"uid"` |
|
} `json:"event"` |
|
} |
|
|
|
if err = unmarshalJSON(resp.Json, &result); err != nil { |
|
return err |
|
} |
|
|
|
if len(result.Event) == 0 { |
|
return nil // Event doesn't exist |
|
} |
|
|
|
// Delete the event node |
|
mutation := &api.Mutation{ |
|
DelNquads: []byte(fmt.Sprintf("<%s> * * .", result.Event[0].UID)), |
|
CommitNow: true, |
|
} |
|
|
|
if _, err = d.Mutate(c, mutation); err != nil { |
|
return fmt.Errorf("failed to delete event: %w", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// DeleteEventBySerial deletes an event by its serial number |
|
func (d *D) DeleteEventBySerial(c context.Context, ser *types.Uint40, ev *event.E) error { |
|
serial := ser.Get() |
|
|
|
// Find the event's UID |
|
query := fmt.Sprintf(`{ |
|
event(func: eq(event.serial, %d)) { |
|
uid |
|
} |
|
}`, serial) |
|
|
|
resp, err := d.Query(c, query) |
|
if err != nil { |
|
return fmt.Errorf("failed to find event for deletion: %w", err) |
|
} |
|
|
|
// Parse UID |
|
var result struct { |
|
Event []struct { |
|
UID string `json:"uid"` |
|
} `json:"event"` |
|
} |
|
|
|
if err = unmarshalJSON(resp.Json, &result); err != nil { |
|
return err |
|
} |
|
|
|
if len(result.Event) == 0 { |
|
return nil // Event doesn't exist |
|
} |
|
|
|
// Delete the event node |
|
mutation := &api.Mutation{ |
|
DelNquads: []byte(fmt.Sprintf("<%s> * * .", result.Event[0].UID)), |
|
CommitNow: true, |
|
} |
|
|
|
if _, err = d.Mutate(c, mutation); err != nil { |
|
return fmt.Errorf("failed to delete event: %w", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// DeleteExpired removes events that have passed their expiration time (NIP-40) |
|
func (d *D) DeleteExpired() { |
|
// Query for events that have an "expiration" tag |
|
// NIP-40: events should have a tag ["expiration", "<unix timestamp>"] |
|
query := `{ |
|
events(func: has(event.tags)) { |
|
uid |
|
event.id |
|
event.tags |
|
event.created_at |
|
} |
|
}` |
|
|
|
resp, err := d.Query(context.Background(), query) |
|
if err != nil { |
|
d.Logger.Errorf("failed to query events for expiration: %v", err) |
|
return |
|
} |
|
|
|
var result struct { |
|
Events []struct { |
|
UID string `json:"uid"` |
|
ID string `json:"event.id"` |
|
Tags string `json:"event.tags"` |
|
CreatedAt int64 `json:"event.created_at"` |
|
} `json:"events"` |
|
} |
|
|
|
if err = unmarshalJSON(resp.Json, &result); err != nil { |
|
d.Logger.Errorf("failed to parse events for expiration: %v", err) |
|
return |
|
} |
|
|
|
now := time.Now().Unix() |
|
deletedCount := 0 |
|
|
|
for _, ev := range result.Events { |
|
// Parse tags |
|
if ev.Tags == "" { |
|
continue |
|
} |
|
|
|
var tags [][]string |
|
if err := json.Unmarshal([]byte(ev.Tags), &tags); err != nil { |
|
continue |
|
} |
|
|
|
// Look for expiration tag |
|
var expirationTime int64 |
|
for _, tag := range tags { |
|
if len(tag) >= 2 && tag[0] == "expiration" { |
|
// Parse expiration timestamp |
|
if _, err := fmt.Sscanf(tag[1], "%d", &expirationTime); err != nil { |
|
continue |
|
} |
|
break |
|
} |
|
} |
|
|
|
// If expiration time found and passed, delete the event |
|
if expirationTime > 0 && now > expirationTime { |
|
mutation := &api.Mutation{ |
|
DelNquads: []byte(fmt.Sprintf("<%s> * * .", ev.UID)), |
|
CommitNow: true, |
|
} |
|
|
|
if _, err := d.Mutate(context.Background(), mutation); err != nil { |
|
d.Logger.Warningf("failed to delete expired event %s: %v", ev.ID, err) |
|
} else { |
|
deletedCount++ |
|
} |
|
} |
|
} |
|
|
|
if deletedCount > 0 { |
|
d.Logger.Infof("deleted %d expired events", deletedCount) |
|
} |
|
} |
|
|
|
// ProcessDelete processes a kind 5 deletion event |
|
func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error) { |
|
if ev.Kind != 5 { |
|
return fmt.Errorf("event is not a deletion event (kind 5)") |
|
} |
|
|
|
// Extract event IDs to delete from tags |
|
for _, tag := range *ev.Tags { |
|
if len(tag.T) >= 2 && string(tag.T[0]) == "e" { |
|
eventID := tag.T[1] |
|
|
|
// Verify the deletion is authorized (author must match or be admin) |
|
if err = d.CheckForDeleted(ev, admins); err != nil { |
|
continue |
|
} |
|
|
|
// Delete the event |
|
if err = d.DeleteEvent(context.Background(), eventID); err != nil { |
|
// Log error but continue with other deletions |
|
d.Logger.Errorf("failed to delete event %s: %v", hex.Enc(eventID), err) |
|
} |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// CheckForDeleted checks if an event has been deleted |
|
func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) { |
|
// Query for delete events (kind 5) that reference this event |
|
evID := hex.Enc(ev.ID[:]) |
|
|
|
query := fmt.Sprintf(`{ |
|
deletes(func: eq(event.kind, 5)) @filter(eq(event.pubkey, %q)) { |
|
uid |
|
event.pubkey |
|
references @filter(eq(event.id, %q)) { |
|
event.id |
|
} |
|
} |
|
}`, hex.Enc(ev.Pubkey), evID) |
|
|
|
resp, err := d.Query(context.Background(), query) |
|
if err != nil { |
|
return fmt.Errorf("failed to check for deletions: %w", err) |
|
} |
|
|
|
var result struct { |
|
Deletes []struct { |
|
UID string `json:"uid"` |
|
Pubkey string `json:"event.pubkey"` |
|
References []struct { |
|
ID string `json:"event.id"` |
|
} `json:"references"` |
|
} `json:"deletes"` |
|
} |
|
|
|
if err = unmarshalJSON(resp.Json, &result); err != nil { |
|
return err |
|
} |
|
|
|
// Check if any delete events reference this event |
|
for _, del := range result.Deletes { |
|
if len(del.References) > 0 { |
|
// Check if deletion is from the author or an admin |
|
delPubkey, _ := hex.Dec(del.Pubkey) |
|
if string(delPubkey) == string(ev.Pubkey) { |
|
return fmt.Errorf("event has been deleted by author") |
|
} |
|
|
|
// Check admins |
|
for _, admin := range admins { |
|
if string(delPubkey) == string(admin) { |
|
return fmt.Errorf("event has been deleted by admin") |
|
} |
|
} |
|
} |
|
} |
|
|
|
return nil |
|
}
|
|
|