neo4j

package
v0.36.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 25, 2025 License: Unlicense Imports: 28 Imported by: 0

README

Neo4j Database Backend

A graph database backend implementation for the ORLY Nostr relay using Neo4j.

Quick Start

1. Start Neo4j
docker run -d --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/password \
  neo4j:5.15
2. Configure Environment

All Neo4j configuration is defined in app/config/config.go and visible via ./orly help:

export ORLY_DB_TYPE=neo4j
export ORLY_NEO4J_URI=bolt://localhost:7687
export ORLY_NEO4J_USER=neo4j
export ORLY_NEO4J_PASSWORD=password

Note: Configuration is centralized in app/config/config.go. Do not use os.Getenv() directly in package code - all environment variables should be passed via the database.DatabaseConfig struct.

3. Run ORLY
./orly

Features

  • Graph-Native Storage: Events, authors, and tags stored as nodes and relationships
  • Unified Tag Model: All tags (including e/p tags) stored as Tag nodes with REFERENCES relationships
  • Efficient Queries: Leverages Neo4j's native graph traversal for tag and social graph queries
  • Cypher Query Language: Powerful, expressive query language for complex filters
  • Automatic Indexing: Unique constraints and indexes for optimal performance
  • Relationship Queries: Native support for event references, mentions, and tags
  • Automatic Migrations: Schema migrations run automatically on startup
  • Web of Trust (WoT) Extensions: Optional support for trust metrics, social graph analysis, and content filtering (see WOT_SPEC.md)

Architecture

See docs/NEO4J_BACKEND.md for comprehensive documentation on:

  • Graph schema design
  • How Nostr REQ messages are implemented in Cypher
  • Performance tuning
  • Development guide
  • Comparison with other backends
Tag-Based e/p Model

All tags, including e (event references) and p (pubkey mentions), are stored through intermediate Tag nodes:

Event -[:TAGGED_WITH]-> Tag{type:'e',value:eventId} -[:REFERENCES]-> Event
Event -[:TAGGED_WITH]-> Tag{type:'p',value:pubkey} -[:REFERENCES]-> NostrUser
Event -[:TAGGED_WITH]-> Tag{type:'t',value:topic}  (no REFERENCES for regular tags)

Benefits:

  • Unified tag querying: #e and #p filter queries work correctly
  • Consistent data model: All tags use the same TAGGED_WITH pattern
  • Graph traversal: Can traverse from events through tags to referenced entities

Migration: Existing databases with direct REFERENCES/MENTIONS relationships are automatically migrated at startup via v3 migration.

Web of Trust (WoT) Extensions

This package includes schema support for Web of Trust trust metrics computation:

  • WOT_SPEC.md - Complete specification of the WoT data model, based on the Brainstorm prototype

    • NostrUser nodes with trust metrics (influence, PageRank, verified counts)
    • NostrUserWotMetricsCard nodes for personalized multi-tenant metrics
    • Social graph relationships (FOLLOWS, MUTES, REPORTS)
    • Cypher schema definitions and example queries
  • ADDITIONAL_REQUIREMENTS.md - Implementation requirements and missing details

    • Algorithm implementations (GrapeRank, Personalized PageRank)
    • Event processing logic for kinds 0, 3, 1984, 10000
    • Multi-tenant architecture and configuration
    • Performance considerations and deployment modes

Note: The WoT schema is applied automatically but WoT features are not yet fully implemented. See ADDITIONAL_REQUIREMENTS.md for the roadmap.

File Structure

Core Implementation
  • neo4j.go - Main database implementation
  • schema.go - Graph schema and index definitions (includes WoT extensions)
  • query-events.go - REQ filter to Cypher translation
  • save-event.go - Event storage with relationship creation
  • fetch-event.go - Event retrieval by serial/ID
  • serial.go - Serial number management
  • markers.go - Metadata key-value storage
  • identity.go - Relay identity management
  • delete.go - Event deletion (NIP-09)
  • subscriptions.go - Subscription management
  • nip43.go - Invite-based ACL (NIP-43)
  • import-export.go - Event import/export
  • logger.go - Logging infrastructure
Documentation
  • README.md - This file
  • WOT_SPEC.md - Web of Trust data model specification
  • ADDITIONAL_REQUIREMENTS.md - WoT implementation requirements and gaps
  • EVENT_PROCESSING_SPEC.md - Event-driven vertex management specification
  • IMPLEMENTATION_SUMMARY.md - Implementation overview and status
  • TESTING.md - Test guide and troubleshooting
  • The Brainstorm prototype_ Neo4j Data Model.html - Original Brainstorm specification document
Tests
  • social-event-processor_test.go - Comprehensive tests for kinds 0, 3, 1984, 10000
  • tag_model_test.go - Tag-based e/p model tests and filter query tests
  • save-event_test.go - Event storage and relationship tests

Testing

Quick Start
# Start Neo4j using docker-compose
cd pkg/neo4j
docker-compose up -d

# Wait for Neo4j to be ready (~30 seconds)
docker-compose logs -f neo4j  # Look for "Started."

# Set Neo4j connection
export ORLY_NEO4J_URI="bolt://localhost:7687"
export ORLY_NEO4J_USER="neo4j"
export ORLY_NEO4J_PASSWORD="testpass123"

# Run all tests
go test -v

# Run social event processor tests
go test -v -run TestSocialEventProcessor

# Cleanup
docker-compose down -v
Test Coverage

The social-event-processor_test.go file contains comprehensive tests for:

  • Kind 0: Profile metadata processing
  • Kind 3: Contact list creation and diff-based updates
  • Kind 1984: Report processing (multiple reports, different types)
  • Kind 10000: Mute list processing
  • Event traceability: Verifies all relationships link to source events
  • Graph state: Validates final graph matches expected state
  • Helper functions: Unit tests for diff computation and p-tag extraction

See TESTING.md for detailed test documentation, troubleshooting, and how to view the graph in Neo4j Browser.

Viewing Test Results

After running tests, explore the graph at http://localhost:7474:

// View all social relationships
MATCH path = (u1:NostrUser)-[r:FOLLOWS|MUTES|REPORTS]->(u2:NostrUser)
RETURN path

// View event processing history
MATCH (evt:ProcessedSocialEvent)
RETURN evt ORDER BY evt.created_at

Example Cypher Queries

Find all events by an author
MATCH (e:Event {pubkey: "abc123..."})
RETURN e
ORDER BY e.created_at DESC
Find events with specific tags
MATCH (e:Event)-[:TAGGED_WITH]->(t:Tag {type: "t", value: "bitcoin"})
RETURN e
Event reference query (e-tags)
MATCH (e:Event)-[:TAGGED_WITH]->(t:Tag {type: "e"})-[:REFERENCES]->(ref:Event)
WHERE e.id = "abc123..."
RETURN e, ref
Mentions query (p-tags)
MATCH (e:Event)-[:TAGGED_WITH]->(t:Tag {type: "p"})-[:REFERENCES]->(u:NostrUser)
WHERE e.id = "abc123..."
RETURN e, u
Social graph query
MATCH (author:NostrUser {pubkey: "abc123..."})
<-[:AUTHORED_BY]-(e:Event)
-[:TAGGED_WITH]->(:Tag {type: "p"})-[:REFERENCES]->(mentioned:NostrUser)
RETURN author, e, mentioned

Performance Tips

  1. Use Limits: Always include LIMIT in queries
  2. Index Usage: Ensure queries use indexed properties (id, kind, created_at)
  3. Parameterize: Use parameterized queries to enable query plan caching
  4. Monitor: Use EXPLAIN and PROFILE to analyze query performance

Limitations

  • Requires external Neo4j database (not embedded)
  • Higher memory usage compared to Badger
  • Metadata still uses Badger (markers, subscriptions)
  • More complex deployment than single-binary solutions

Why Neo4j for Nostr?

Nostr is inherently a social graph with heavy relationship queries:

  • Event references (e-tags) → Graph edges
  • Author mentions (p-tags) → Graph edges
  • Follow relationships → Graph structure
  • Thread traversal → Path queries

Neo4j excels at these patterns, making it a natural fit for relationship-heavy Nostr queries.

License

Same as ORLY relay project.

Documentation

Overview

Package neo4j provides hex utilities for normalizing pubkeys and event IDs.

The nostr library applies binary optimization to e/p tags, storing 64-character hex strings as 33-byte binary (32 bytes + null terminator). This file provides utilities to ensure all pubkeys and event IDs stored in Neo4j are in consistent lowercase hex format.

Package neo4j provides a Neo4j-based implementation of the database interface. Neo4j is a native graph database optimized for relationship-heavy queries, making it ideal for Nostr's social graph and event reference patterns.

Index

Constants

View Source
const (
	// BinaryEncodedLen is the length of a binary-encoded 32-byte hash with null terminator
	BinaryEncodedLen = 33
	// HexEncodedLen is the length of a hex-encoded 32-byte hash (pubkey or event ID)
	HexEncodedLen = 64
	// HashLen is the raw length of a hash (pubkey/event ID)
	HashLen = 32
)

Tag binary encoding constants (matching the nostr library)

Variables

This section is empty.

Functions

func ExtractETagValue added in v0.32.2

func ExtractETagValue(t *tag.T) string

ExtractETagValue extracts an event ID from an e-tag, handling binary encoding. Returns lowercase hex string suitable for Neo4j storage. Returns empty string if the tag doesn't have a valid value.

func ExtractPTagValue added in v0.32.2

func ExtractPTagValue(t *tag.T) string

ExtractPTagValue extracts a pubkey from a p-tag, handling binary encoding. Returns lowercase hex string suitable for Neo4j storage. Returns empty string if the tag doesn't have a valid value.

func IsBinaryEncoded added in v0.32.2

func IsBinaryEncoded(val []byte) bool

IsBinaryEncoded checks if a value is stored in the nostr library's binary-optimized format

func IsValidHexPubkey added in v0.32.2

func IsValidHexPubkey(s string) bool

IsValidHexPubkey checks if a string is a valid 64-character hex pubkey

func NewLogger

func NewLogger(logLevel int, label string) (l *logger)

NewLogger creates a new neo4j logger.

func NormalizePubkeyHex added in v0.32.2

func NormalizePubkeyHex(val []byte) string

NormalizePubkeyHex ensures a pubkey/event ID is in lowercase hex format. It handles: - Binary-encoded values (33 bytes with null terminator) -> converts to lowercase hex - Raw binary values (32 bytes) -> converts to lowercase hex - Uppercase hex strings -> converts to lowercase - Already lowercase hex -> returns as-is

This should be used for all pubkeys and event IDs before storing in Neo4j to prevent duplicate nodes due to case differences.

Types

type CollectedResult added in v0.30.0

type CollectedResult struct {
	// contains filtered or unexported fields
}

CollectedResult wraps pre-fetched Neo4j records for iteration after session close This is necessary because Neo4j results are lazy and need an open session for iteration

func (*CollectedResult) Err added in v0.32.1

func (r *CollectedResult) Err() error

Err returns any error from iteration (always nil for pre-collected results) This method satisfies the resultiter.Neo4jResultIterator interface

func (*CollectedResult) Len added in v0.30.0

func (r *CollectedResult) Len() int

Len returns the number of records

func (*CollectedResult) Next added in v0.30.0

func (r *CollectedResult) Next(ctx context.Context) bool

Next advances to the next record, returning true if there is one

func (*CollectedResult) Record added in v0.30.0

func (r *CollectedResult) Record() *neo4j.Record

Record returns the current record

type GraphAdapter added in v0.35.0

type GraphAdapter struct {
	// contains filtered or unexported fields
}

GraphAdapter wraps a Neo4j database instance and implements graph.GraphDatabase interface. This allows the graph executor to call database traversal methods without the database package importing the graph package.

func NewGraphAdapter added in v0.35.0

func NewGraphAdapter(db *N) *GraphAdapter

NewGraphAdapter creates a new GraphAdapter wrapping the given Neo4j database.

func (*GraphAdapter) FindMentions added in v0.35.0

func (a *GraphAdapter) FindMentions(pubkey []byte, kinds []uint16) (graph.GraphResultI, error)

FindMentions implements graph.GraphDatabase.

func (*GraphAdapter) TraverseFollowers added in v0.35.0

func (a *GraphAdapter) TraverseFollowers(seedPubkey []byte, maxDepth int) (graph.GraphResultI, error)

TraverseFollowers implements graph.GraphDatabase.

func (*GraphAdapter) TraverseFollows added in v0.35.0

func (a *GraphAdapter) TraverseFollows(seedPubkey []byte, maxDepth int) (graph.GraphResultI, error)

TraverseFollows implements graph.GraphDatabase.

func (*GraphAdapter) TraverseThread added in v0.35.0

func (a *GraphAdapter) TraverseThread(seedEventID []byte, maxDepth int, direction string) (graph.GraphResultI, error)

TraverseThread implements graph.GraphDatabase.

type GraphResult added in v0.35.0

type GraphResult struct {
	// PubkeysByDepth maps depth -> pubkeys first discovered at that depth.
	// Each pubkey appears ONLY in the array for the depth where it was first seen.
	// Depth 1 = direct connections, Depth 2 = connections of connections, etc.
	PubkeysByDepth map[int][]string

	// EventsByDepth maps depth -> event IDs discovered at that depth.
	// Used for thread traversal queries.
	EventsByDepth map[int][]string

	// FirstSeenPubkey tracks which depth each pubkey was first discovered.
	// Key is pubkey hex, value is the depth (1-indexed).
	FirstSeenPubkey map[string]int

	// FirstSeenEvent tracks which depth each event was first discovered.
	// Key is event ID hex, value is the depth (1-indexed).
	FirstSeenEvent map[string]int

	// TotalPubkeys is the count of unique pubkeys discovered across all depths.
	TotalPubkeys int

	// TotalEvents is the count of unique events discovered across all depths.
	TotalEvents int
}

GraphResult contains depth-organized traversal results for graph queries. It tracks pubkeys and events discovered at each depth level, ensuring each entity appears only at the depth where it was first discovered.

This is the Neo4j implementation that mirrors the Badger implementation in pkg/database/graph-result.go, implementing the graph.GraphResultI interface.

func NewGraphResult added in v0.35.0

func NewGraphResult() *GraphResult

NewGraphResult creates a new initialized GraphResult.

func (*GraphResult) AddEventAtDepth added in v0.35.0

func (r *GraphResult) AddEventAtDepth(eventIDHex string, depth int) bool

AddEventAtDepth adds an event ID to the result at the specified depth if not already seen. Returns true if the event was added (first time seen), false if already exists.

func (*GraphResult) AddPubkeyAtDepth added in v0.35.0

func (r *GraphResult) AddPubkeyAtDepth(pubkeyHex string, depth int) bool

AddPubkeyAtDepth adds a pubkey to the result at the specified depth if not already seen. Returns true if the pubkey was added (first time seen), false if already exists.

func (*GraphResult) GetAllEvents added in v0.35.0

func (r *GraphResult) GetAllEvents() []string

GetAllEvents returns all event IDs discovered across all depths.

func (*GraphResult) GetAllPubkeys added in v0.35.0

func (r *GraphResult) GetAllPubkeys() []string

GetAllPubkeys returns all pubkeys discovered across all depths.

func (*GraphResult) GetDepthsSorted added in v0.35.0

func (r *GraphResult) GetDepthsSorted() []int

GetDepthsSorted returns all depths that have pubkeys, sorted ascending.

func (*GraphResult) GetEventDepthsSorted added in v0.35.0

func (r *GraphResult) GetEventDepthsSorted() []int

GetEventDepthsSorted returns all depths that have events, sorted ascending.

func (*GraphResult) GetEventsByDepth added in v0.35.0

func (r *GraphResult) GetEventsByDepth() map[int][]string

GetEventsByDepth returns the EventsByDepth map for external access.

func (*GraphResult) GetPubkeysByDepth added in v0.35.0

func (r *GraphResult) GetPubkeysByDepth() map[int][]string

GetPubkeysByDepth returns the PubkeysByDepth map for external access.

func (*GraphResult) GetTotalEvents added in v0.35.0

func (r *GraphResult) GetTotalEvents() int

GetTotalEvents returns the total event count for external access.

func (*GraphResult) GetTotalPubkeys added in v0.35.0

func (r *GraphResult) GetTotalPubkeys() int

GetTotalPubkeys returns the total pubkey count for external access.

func (*GraphResult) HasEvent added in v0.35.0

func (r *GraphResult) HasEvent(eventIDHex string) bool

HasEvent returns true if the event has been discovered at any depth.

func (*GraphResult) HasPubkey added in v0.35.0

func (r *GraphResult) HasPubkey(pubkeyHex string) bool

HasPubkey returns true if the pubkey has been discovered at any depth.

func (*GraphResult) ToDepthArrays added in v0.35.0

func (r *GraphResult) ToDepthArrays() [][]string

ToDepthArrays converts the result to the response format: array of arrays. Index 0 = depth 1 pubkeys, Index 1 = depth 2 pubkeys, etc. Empty arrays are included for depths with no pubkeys to maintain index alignment.

func (*GraphResult) ToEventDepthArrays added in v0.35.0

func (r *GraphResult) ToEventDepthArrays() [][]string

ToEventDepthArrays converts event results to the response format: array of arrays. Index 0 = depth 1 events, Index 1 = depth 2 events, etc.

type Migration added in v0.32.6

type Migration struct {
	Version     string
	Description string
	Migrate     func(ctx context.Context, n *N) error
}

Migration represents a database migration with a version identifier

type N

type N struct {
	Logger *logger
	// contains filtered or unexported fields
}

N implements the database.Database interface using Neo4j as the storage backend

func New

func New(
	ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string,
) (
	n *N, err error,
)

New creates a new Neo4j-based database instance with default configuration. This is provided for backward compatibility with existing callers (tests, etc.). For full configuration control, use NewWithConfig instead.

func NewWithConfig added in v0.31.8

func NewWithConfig(
	ctx context.Context, cancel context.CancelFunc, cfg *database.DatabaseConfig,
) (
	n *N, err error,
)

NewWithConfig creates a new Neo4j-based database instance with full configuration. Configuration is passed from the centralized app config via DatabaseConfig.

func (*N) AddNIP43Member

func (n *N) AddNIP43Member(pubkey []byte, inviteCode string) error

AddNIP43Member adds a member using an invite code

func (*N) CacheEvents added in v0.29.11

func (n *N) CacheEvents(f *filter.F, events event.S)

CacheEvents caches events (not implemented for Neo4j)

func (*N) CacheMarshaledJSON

func (n *N) CacheMarshaledJSON(f *filter.F, marshaledJSON [][]byte)

CacheMarshaledJSON caches marshaled JSON results (not implemented for Neo4j)

func (*N) CheckForDeleted

func (n *N) CheckForDeleted(ev *event.E, admins [][]byte) error

CheckForDeleted checks if an event has been deleted

func (*N) Close

func (n *N) Close() (err error)

Close closes the database

func (*N) CountEvents

func (n *N) CountEvents(c context.Context, f *filter.F) (
	count int, approximate bool, err error,
)

CountEvents counts events matching a filter

func (*N) DeleteEvent

func (n *N) DeleteEvent(c context.Context, eid []byte) error

DeleteEvent deletes an event by its ID

func (*N) DeleteEventBySerial

func (n *N) DeleteEventBySerial(c context.Context, ser *types.Uint40, ev *event.E) error

DeleteEventBySerial deletes an event by its serial number

func (*N) DeleteExpired

func (n *N) DeleteExpired()

DeleteExpired deletes expired events based on NIP-40 expiration tags Events with an expiration property > 0 and <= current time are deleted

func (*N) DeleteInviteCode

func (n *N) DeleteInviteCode(code string) error

DeleteInviteCode removes an invite code

func (*N) DeleteMarker

func (n *N) DeleteMarker(key string) error

DeleteMarker removes a metadata marker

func (*N) Driver added in v0.35.1

func (n *N) Driver() neo4j.DriverWithContext

Driver returns the Neo4j driver for use in rate limiting.

func (*N) EventIdsBySerial

func (n *N) EventIdsBySerial(start uint64, count int) (
	evs []uint64, err error,
)

EventIdsBySerial retrieves event IDs by serial range (stub)

func (*N) ExecuteRead

func (n *N) ExecuteRead(ctx context.Context, cypher string, params map[string]any) (*CollectedResult, error)

ExecuteRead executes a read query against Neo4j with rate limiting and retry Returns a collected result that can be iterated after the session closes

func (*N) ExecuteWrite

func (n *N) ExecuteWrite(ctx context.Context, cypher string, params map[string]any) (neo4j.ResultWithContext, error)

ExecuteWrite executes a write query against Neo4j with rate limiting and retry

func (*N) ExecuteWriteTransaction

func (n *N) ExecuteWriteTransaction(ctx context.Context, work func(tx neo4j.ManagedTransaction) (any, error)) (any, error)

ExecuteWriteTransaction executes a transactional write operation with rate limiting

func (*N) Export

func (n *N) Export(c context.Context, w io.Writer, pubkeys ...[]byte)

Export exports events to a writer (JSONL format) If pubkeys are provided, only exports events from those authors Otherwise exports all events

func (*N) ExtendBlossomSubscription

func (n *N) ExtendBlossomSubscription(
	pubkey []byte, tier string, storageMB int64, daysExtended int,
) error

ExtendBlossomSubscription extends a Blossom storage subscription

func (*N) ExtendSubscription

func (n *N) ExtendSubscription(pubkey []byte, days int) error

ExtendSubscription extends a subscription by the specified number of days

func (*N) FetchEventBySerial

func (n *N) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error)

FetchEventBySerial retrieves an event by its serial number

func (*N) FetchEventsBySerials

func (n *N) FetchEventsBySerials(serials []*types.Uint40) (
	events map[uint64]*event.E, err error,
)

FetchEventsBySerials retrieves multiple events by their serial numbers

func (*N) FindMentions added in v0.35.0

func (n *N) FindMentions(pubkey []byte, kinds []uint16) (graph.GraphResultI, error)

FindMentions finds events that mention a pubkey via p-tags. This returns events grouped by depth, where depth represents how the events relate:

  • Depth 1: Events that directly mention the seed pubkey
  • Depth 2+: Not typically used for mentions (reserved for future expansion)

The kinds parameter filters which event kinds to include (e.g., [1] for notes only, [1,7] for notes and reactions, etc.)

Uses Neo4j MENTIONS relationships created by SaveEvent when processing p-tags.

func (*N) FindMentionsByPubkeys added in v0.35.0

func (n *N) FindMentionsByPubkeys(pubkeys []string, kinds []uint16) (*GraphResult, error)

FindMentionsByPubkeys returns events that mention any of the given pubkeys. Useful for finding mentions across a set of followed accounts.

func (*N) FindMentionsFromHex added in v0.35.0

func (n *N) FindMentionsFromHex(pubkeyHex string, kinds []uint16) (*GraphResult, error)

FindMentionsFromHex is a convenience wrapper that accepts hex-encoded pubkey.

func (*N) GetAllNIP43Members

func (n *N) GetAllNIP43Members() ([][]byte, error)

GetAllNIP43Members retrieves all member pubkeys

func (*N) GetBlossomStorageQuota

func (n *N) GetBlossomStorageQuota(pubkey []byte) (quotaMB int64, err error)

GetBlossomStorageQuota retrieves the storage quota for a pubkey

func (*N) GetCachedEvents added in v0.29.11

func (n *N) GetCachedEvents(f *filter.F) (event.S, bool)

GetCachedEvents retrieves cached events (not implemented for Neo4j)

func (*N) GetCachedJSON

func (n *N) GetCachedJSON(f *filter.F) ([][]byte, bool)

GetCachedJSON returns cached query results (not implemented for Neo4j)

func (*N) GetFullIdPubkeyBySerial

func (n *N) GetFullIdPubkeyBySerial(ser *types.Uint40) (
	fidpk *store.IdPkTs, err error,
)

GetFullIdPubkeyBySerial retrieves ID and pubkey for a serial number

func (*N) GetFullIdPubkeyBySerials

func (n *N) GetFullIdPubkeyBySerials(sers []*types.Uint40) (
	fidpks []*store.IdPkTs, err error,
)

GetFullIdPubkeyBySerials retrieves IDs and pubkeys for multiple serials

func (*N) GetMarker

func (n *N) GetMarker(key string) (value []byte, err error)

GetMarker retrieves a metadata marker

func (*N) GetNIP43Membership

func (n *N) GetNIP43Membership(pubkey []byte) (*database.NIP43Membership, error)

GetNIP43Membership retrieves membership information

func (*N) GetOrCreateRelayIdentitySecret

func (n *N) GetOrCreateRelayIdentitySecret() (skb []byte, err error)

GetOrCreateRelayIdentitySecret retrieves or creates the relay identity

func (*N) GetPaymentHistory

func (n *N) GetPaymentHistory(pubkey []byte) ([]database.Payment, error)

GetPaymentHistory retrieves payment history for a pubkey

func (*N) GetRelayIdentitySecret

func (n *N) GetRelayIdentitySecret() (skb []byte, err error)

GetRelayIdentitySecret retrieves the relay's identity secret key

func (*N) GetSerialById

func (n *N) GetSerialById(id []byte) (ser *types.Uint40, err error)

GetSerialById retrieves the serial number for an event ID

func (*N) GetSerialsByIds

func (n *N) GetSerialsByIds(ids *tag.T) (
	serials map[string]*types.Uint40, err error,
)

GetSerialsByIds retrieves serial numbers for multiple event IDs

func (*N) GetSerialsByIdsWithFilter

func (n *N) GetSerialsByIdsWithFilter(
	ids *tag.T, fn func(ev *event.E, ser *types.Uint40) bool,
) (serials map[string]*types.Uint40, err error)

GetSerialsByIdsWithFilter retrieves serials with a filter function

func (*N) GetSerialsByRange

func (n *N) GetSerialsByRange(idx database.Range) (
	serials types.Uint40s, err error,
)

GetSerialsByRange retrieves serials within a range

func (*N) GetSerialsFromFilter

func (n *N) GetSerialsFromFilter(f *filter.F) (serials types.Uint40s, err error)

GetSerialsFromFilter returns event serials matching a filter

func (*N) GetSubscription

func (n *N) GetSubscription(pubkey []byte) (*database.Subscription, error)

GetSubscription retrieves subscription information for a pubkey

func (*N) GetThreadParents added in v0.35.0

func (n *N) GetThreadParents(eventID []byte) (*GraphResult, error)

GetThreadParents finds events that a given event references (its parents/quotes).

func (*N) GetThreadReplies added in v0.35.0

func (n *N) GetThreadReplies(eventID []byte, kinds []uint16) (*GraphResult, error)

GetThreadReplies finds all direct replies to an event. This is a convenience method that returns events at depth 1 with inbound direction.

func (*N) HasMarker

func (n *N) HasMarker(key string) bool

HasMarker checks if a marker exists

func (*N) Import

func (n *N) Import(rr io.Reader)

Import imports events from a reader (JSONL format)

func (*N) ImportEventsFromReader

func (n *N) ImportEventsFromReader(ctx context.Context, rr io.Reader) error

ImportEventsFromReader imports events from a reader

func (*N) ImportEventsFromStrings

func (n *N) ImportEventsFromStrings(
	ctx context.Context,
	eventJSONs []string,
	policyManager interface {
		CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error)
	},
) error

ImportEventsFromStrings imports events from JSON strings

func (*N) Init

func (n *N) Init(path string) (err error)

Init initializes the database with a given path (no-op, path set in New)

func (*N) InvalidateQueryCache

func (n *N) InvalidateQueryCache()

InvalidateQueryCache invalidates the query cache (not implemented for Neo4j)

func (*N) IsFirstTimeUser

func (n *N) IsFirstTimeUser(pubkey []byte) (bool, error)

IsFirstTimeUser checks if this is the first time a user is accessing the relay

func (*N) IsNIP43Member

func (n *N) IsNIP43Member(pubkey []byte) (isMember bool, err error)

IsNIP43Member checks if a pubkey is a member

func (*N) IsSubscriptionActive

func (n *N) IsSubscriptionActive(pubkey []byte) (bool, error)

IsSubscriptionActive checks if a pubkey has an active subscription

func (*N) MaxConcurrentQueries added in v0.35.1

func (n *N) MaxConcurrentQueries() int

MaxConcurrentQueries returns the maximum concurrent query limit.

func (*N) Path

func (n *N) Path() string

Path returns the data directory path

func (*N) ProcessDelete

func (n *N) ProcessDelete(ev *event.E, admins [][]byte) error

ProcessDelete processes a kind 5 deletion event

func (*N) PublishNIP43MembershipEvent

func (n *N) PublishNIP43MembershipEvent(kind int, pubkey []byte) error

PublishNIP43MembershipEvent publishes a membership event

func (*N) QueryAllVersions

func (n *N) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error)

QueryAllVersions retrieves all versions of events matching the filter

func (*N) QueryDeleteEventsByTargetId

func (n *N) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (
	evs event.S, err error,
)

QueryDeleteEventsByTargetId retrieves delete events targeting a specific event ID

func (*N) QueryEvents

func (n *N) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error)

QueryEvents retrieves events matching the given filter

func (*N) QueryEventsWithOptions

func (n *N) QueryEventsWithOptions(
	c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool,
) (evs event.S, err error)

QueryEventsWithOptions retrieves events with specific options

func (*N) QueryForIds

func (n *N) QueryForIds(c context.Context, f *filter.F) (
	idPkTs []*store.IdPkTs, err error,
)

QueryForIds retrieves event IDs matching a filter

func (*N) QueryForSerials

func (n *N) QueryForSerials(c context.Context, f *filter.F) (
	serials types.Uint40s, err error,
)

QueryForSerials retrieves event serials matching a filter

func (*N) QuerySem added in v0.35.1

func (n *N) QuerySem() chan struct{}

QuerySem returns the query semaphore for use in rate limiting.

func (*N) Ready

func (n *N) Ready() <-chan struct{}

Ready returns a channel that closes when the database is ready to serve requests. This allows callers to wait for database warmup to complete.

func (*N) RecordPayment

func (n *N) RecordPayment(
	pubkey []byte, amount int64, invoice, preimage string,
) error

RecordPayment records a payment for subscription extension

func (*N) RemoveNIP43Member

func (n *N) RemoveNIP43Member(pubkey []byte) error

RemoveNIP43Member removes a member

func (*N) RunMigrations

func (n *N) RunMigrations()

RunMigrations executes all pending migrations

func (*N) SaveEvent

func (n *N) SaveEvent(c context.Context, ev *event.E) (exists bool, err error)

SaveEvent stores a Nostr event in the Neo4j database. It creates event nodes and relationships for authors, tags, and references. This method leverages Neo4j's graph capabilities to model Nostr's social graph naturally.

For social graph events (kinds 0, 3, 1984, 10000), it additionally processes them to maintain NostrUser nodes and FOLLOWS/MUTES/REPORTS relationships with event traceability.

To prevent Neo4j stack overflow errors with events containing thousands of tags, tags are processed in batches using UNWIND instead of generating inline Cypher.

func (*N) SetLogLevel

func (n *N) SetLogLevel(level string)

SetLogLevel sets the logging level

func (*N) SetMarker

func (n *N) SetMarker(key string, value []byte) error

SetMarker sets a metadata marker

func (*N) SetRelayIdentitySecret

func (n *N) SetRelayIdentitySecret(skb []byte) error

SetRelayIdentitySecret sets the relay's identity secret key

func (*N) StoreInviteCode

func (n *N) StoreInviteCode(code string, expiresAt time.Time) error

StoreInviteCode stores an invite code with expiration

func (*N) Sync

func (n *N) Sync() (err error)

Sync flushes pending writes (Neo4j handles persistence automatically)

func (*N) TraverseFollowers added in v0.35.0

func (n *N) TraverseFollowers(seedPubkey []byte, maxDepth int) (graph.GraphResultI, error)

TraverseFollowers performs BFS traversal to find who follows the seed pubkey. This is the reverse of TraverseFollows - it finds users whose kind-3 lists contain the target pubkey(s).

Uses Neo4j's native path queries, but in reverse direction:

  • Depth 1: Users who directly follow the seed (follower)-[:FOLLOWS]->(seed)
  • Depth 2: Users who follow anyone at depth 1 (followers of followers)
  • etc.

func (*N) TraverseFollowersFromHex added in v0.35.0

func (n *N) TraverseFollowersFromHex(seedPubkeyHex string, maxDepth int) (*GraphResult, error)

TraverseFollowersFromHex is a convenience wrapper that accepts hex-encoded pubkey.

func (*N) TraverseFollows added in v0.35.0

func (n *N) TraverseFollows(seedPubkey []byte, maxDepth int) (graph.GraphResultI, error)

TraverseFollows performs BFS traversal of the follow graph starting from a seed pubkey. Returns pubkeys grouped by first-discovered depth (no duplicates across depths).

Uses Neo4j's native path queries with FOLLOWS relationships created by the social event processor from kind 3 contact list events.

The traversal works by using variable-length path patterns:

  • Depth 1: Direct follows (seed)-[:FOLLOWS]->(followed)
  • Depth 2: Follows of follows (seed)-[:FOLLOWS*2]->(followed)
  • etc.

Each pubkey appears only at the depth where it was first discovered.

func (*N) TraverseFollowsFromHex added in v0.35.0

func (n *N) TraverseFollowsFromHex(seedPubkeyHex string, maxDepth int) (*GraphResult, error)

TraverseFollowsFromHex is a convenience wrapper that accepts hex-encoded pubkey.

func (*N) TraverseThread added in v0.35.0

func (n *N) TraverseThread(seedEventID []byte, maxDepth int, direction string) (graph.GraphResultI, error)

TraverseThread performs BFS traversal of thread structure via e-tags. Starting from a seed event, it finds all replies/references at each depth.

The traversal works bidirectionally using REFERENCES relationships:

  • Inbound: Events that reference the seed (replies, reactions, reposts)
  • Outbound: Events that the seed references (parents, quoted posts)

Note: REFERENCES relationships are only created if the referenced event exists in the database at the time of saving. This means some references may be missing if events were stored out of order.

Parameters:

  • seedEventID: The event ID to start traversal from
  • maxDepth: Maximum depth to traverse
  • direction: "both" (default), "inbound" (replies to seed), "outbound" (seed's references)

func (*N) TraverseThreadFromHex added in v0.35.0

func (n *N) TraverseThreadFromHex(seedEventIDHex string, maxDepth int, direction string) (*GraphResult, error)

TraverseThreadFromHex is a convenience wrapper that accepts hex-encoded event ID.

func (*N) ValidateInviteCode

func (n *N) ValidateInviteCode(code string) (valid bool, err error)

ValidateInviteCode checks if an invite code is valid

func (*N) Wipe

func (n *N) Wipe() (err error)

Wipe removes all data and re-applies schema

func (*N) WouldReplaceEvent

func (n *N) WouldReplaceEvent(ev *event.E) (bool, types.Uint40s, error)

WouldReplaceEvent checks if an event would replace existing events This handles replaceable events (kinds 0, 3, and 10000-19999) and parameterized replaceable events (kinds 30000-39999)

type ProcessedSocialEvent added in v0.30.0

type ProcessedSocialEvent struct {
	EventID           string
	EventKind         int
	Pubkey            string
	CreatedAt         int64
	ProcessedAt       int64
	RelationshipCount int
	SupersededBy      *string // nil if still active
}

ProcessedSocialEvent represents a processed social graph event in Neo4j

type SocialEventProcessor added in v0.30.0

type SocialEventProcessor struct {
	// contains filtered or unexported fields
}

SocialEventProcessor handles kind 0, 3, 1984, 10000 events for social graph management

func NewSocialEventProcessor added in v0.30.0

func NewSocialEventProcessor(db *N) *SocialEventProcessor

NewSocialEventProcessor creates a new social event processor

func (*SocialEventProcessor) BatchProcessContactLists added in v0.30.0

func (p *SocialEventProcessor) BatchProcessContactLists(ctx context.Context, events []*event.E) error

BatchProcessContactLists processes multiple contact list events in order

func (*SocialEventProcessor) ProcessSocialEvent added in v0.30.0

func (p *SocialEventProcessor) ProcessSocialEvent(ctx context.Context, ev *event.E) error

ProcessSocialEvent routes events to appropriate handlers based on kind

type UpdateContactListParams added in v0.30.0

type UpdateContactListParams struct {
	AuthorPubkey   string
	NewEventID     string
	OldEventID     string
	CreatedAt      int64
	AddedFollows   []string
	RemovedFollows []string
	TotalFollows   int
}

UpdateContactListParams holds parameters for contact list graph update

type UpdateMuteListParams added in v0.30.0

type UpdateMuteListParams struct {
	AuthorPubkey string
	NewEventID   string
	OldEventID   string
	CreatedAt    int64
	AddedMutes   []string
	RemovedMutes []string
	TotalMutes   int
}

UpdateMuteListParams holds parameters for mute list graph update

Source Files

  • delete.go
  • fetch-event.go
  • graph-adapter.go
  • graph-follows.go
  • graph-mentions.go
  • graph-result.go
  • graph-thread.go
  • hex_utils.go
  • identity.go
  • import-export.go
  • logger.go
  • markers.go
  • migrations.go
  • neo4j.go
  • nip43.go
  • query-events.go
  • save-event.go
  • schema.go
  • serial.go
  • social-event-processor.go
  • subscriptions.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL