Some checks failed
Go / build-and-release (push) Has been cancelled
Curation Mode: - Three-tier publisher classification: Trusted, Blacklisted, Unclassified - Per-pubkey rate limiting (default 50/day) for unclassified users - IP flood protection (default 500/day) with automatic banning - Event kind allow-listing via categories, ranges, and custom kinds - Query filtering hides blacklisted pubkey events (admin/owner exempt) - Web UI for managing trusted/blacklisted pubkeys and configuration - NIP-86 API endpoints for all curation management operations Graph Query Extension: - Complete reference aggregation for Badger and Neo4j backends - E-tag graph backfill migration (v8) runs automatically on startup - Configuration options: ORLY_GRAPH_QUERIES_ENABLED, MAX_DEPTH, etc. - NIP-11 advertisement of graph query capabilities Files modified: - app/handle-nip86-curating.go: NIP-86 curation API handlers (new) - app/web/src/CurationView.svelte: Curation management UI (new) - app/web/src/kindCategories.js: Kind category definitions (new) - pkg/acl/curating.go: Curating ACL implementation (new) - pkg/database/curating-acl.go: Database layer for curation (new) - pkg/neo4j/graph-refs.go: Neo4j ref collection (new) - pkg/database/migrations.go: E-tag graph backfill migration - pkg/protocol/graph/executor.go: Reference aggregation support - app/handle-event.go: Curation config event processing - app/handle-req.go: Blacklist filtering for queries - docs/GRAPH_QUERIES_REMAINING_PLAN.md: Updated completion status 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package neo4j
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// AddInboundRefsToResult collects inbound references (events that reference discovered items)
|
|
// for events at a specific depth in the result.
|
|
//
|
|
// For example, if you have a follows graph result and want to find all kind-7 reactions
|
|
// to posts by users at depth 1, this collects those reactions and adds them to result.InboundRefs.
|
|
//
|
|
// Parameters:
|
|
// - result: The graph result to augment with ref data
|
|
// - depth: The depth at which to collect refs (0 = all depths)
|
|
// - kinds: Event kinds to collect (e.g., [7] for reactions, [6] for reposts)
|
|
func (n *N) AddInboundRefsToResult(result *GraphResult, depth int, kinds []uint16) error {
|
|
ctx := context.Background()
|
|
|
|
// Get pubkeys to find refs for
|
|
var pubkeys []string
|
|
if depth == 0 {
|
|
pubkeys = result.GetAllPubkeys()
|
|
} else {
|
|
pubkeys = result.GetPubkeysAtDepth(depth)
|
|
}
|
|
|
|
if len(pubkeys) == 0 {
|
|
n.Logger.Debugf("AddInboundRefsToResult: no pubkeys at depth %d", depth)
|
|
return nil
|
|
}
|
|
|
|
// Convert kinds to int64 for Neo4j
|
|
kindsInt := make([]int64, len(kinds))
|
|
for i, k := range kinds {
|
|
kindsInt[i] = int64(k)
|
|
}
|
|
|
|
// Query for events by these pubkeys and their inbound references
|
|
// This finds: (ref:Event)-[:REFERENCES]->(authored:Event)<-[:AUTHORED_BY]-(u:NostrUser)
|
|
// where the referencing event has the specified kinds
|
|
cypher := `
|
|
UNWIND $pubkeys AS pk
|
|
MATCH (u:NostrUser {pubkey: pk})<-[:AUTHORED_BY]-(authored:Event)
|
|
WHERE authored.kind IN [1, 30023]
|
|
MATCH (ref:Event)-[:REFERENCES]->(authored)
|
|
WHERE ref.kind IN $kinds
|
|
RETURN authored.id AS target_id, ref.id AS ref_id, ref.kind AS ref_kind
|
|
`
|
|
|
|
params := map[string]any{
|
|
"pubkeys": pubkeys,
|
|
"kinds": kindsInt,
|
|
}
|
|
|
|
queryResult, err := n.ExecuteRead(ctx, cypher, params)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query inbound refs: %w", err)
|
|
}
|
|
|
|
refCount := 0
|
|
for queryResult.Next(ctx) {
|
|
record := queryResult.Record()
|
|
|
|
targetID, ok := record.Values[0].(string)
|
|
if !ok || targetID == "" {
|
|
continue
|
|
}
|
|
|
|
refID, ok := record.Values[1].(string)
|
|
if !ok || refID == "" {
|
|
continue
|
|
}
|
|
|
|
refKind, ok := record.Values[2].(int64)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
result.AddInboundRef(uint16(refKind), strings.ToLower(targetID), strings.ToLower(refID))
|
|
refCount++
|
|
}
|
|
|
|
n.Logger.Debugf("AddInboundRefsToResult: collected %d refs for %d pubkeys", refCount, len(pubkeys))
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddOutboundRefsToResult collects outbound references (events referenced by discovered items).
|
|
//
|
|
// For example, find all events that posts by users at depth 1 reference (quoted posts, replied-to posts).
|
|
func (n *N) AddOutboundRefsToResult(result *GraphResult, depth int, kinds []uint16) error {
|
|
ctx := context.Background()
|
|
|
|
// Get pubkeys to find refs for
|
|
var pubkeys []string
|
|
if depth == 0 {
|
|
pubkeys = result.GetAllPubkeys()
|
|
} else {
|
|
pubkeys = result.GetPubkeysAtDepth(depth)
|
|
}
|
|
|
|
if len(pubkeys) == 0 {
|
|
n.Logger.Debugf("AddOutboundRefsToResult: no pubkeys at depth %d", depth)
|
|
return nil
|
|
}
|
|
|
|
// Convert kinds to int64 for Neo4j
|
|
kindsInt := make([]int64, len(kinds))
|
|
for i, k := range kinds {
|
|
kindsInt[i] = int64(k)
|
|
}
|
|
|
|
// Query for events by these pubkeys and their outbound references
|
|
// This finds: (authored:Event)-[:REFERENCES]->(ref:Event)
|
|
// where the authored event has the specified kinds
|
|
cypher := `
|
|
UNWIND $pubkeys AS pk
|
|
MATCH (u:NostrUser {pubkey: pk})<-[:AUTHORED_BY]-(authored:Event)
|
|
WHERE authored.kind IN $kinds
|
|
MATCH (authored)-[:REFERENCES]->(ref:Event)
|
|
RETURN authored.id AS source_id, ref.id AS ref_id, authored.kind AS source_kind
|
|
`
|
|
|
|
params := map[string]any{
|
|
"pubkeys": pubkeys,
|
|
"kinds": kindsInt,
|
|
}
|
|
|
|
queryResult, err := n.ExecuteRead(ctx, cypher, params)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query outbound refs: %w", err)
|
|
}
|
|
|
|
refCount := 0
|
|
for queryResult.Next(ctx) {
|
|
record := queryResult.Record()
|
|
|
|
sourceID, ok := record.Values[0].(string)
|
|
if !ok || sourceID == "" {
|
|
continue
|
|
}
|
|
|
|
refID, ok := record.Values[1].(string)
|
|
if !ok || refID == "" {
|
|
continue
|
|
}
|
|
|
|
sourceKind, ok := record.Values[2].(int64)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
result.AddOutboundRef(uint16(sourceKind), strings.ToLower(sourceID), strings.ToLower(refID))
|
|
refCount++
|
|
}
|
|
|
|
n.Logger.Debugf("AddOutboundRefsToResult: collected %d refs from %d pubkeys", refCount, len(pubkeys))
|
|
|
|
return nil
|
|
}
|