Implement Tag-based e/p model for Neo4j backend (v0.36.0)
Some checks failed
Go / build-and-release (push) Has been cancelled

- Add unified Tag-based model where e/p tags create intermediate Tag nodes
  with REFERENCES relationships to Event/NostrUser nodes
- Update save-event.go: addPTagsInBatches and addETagsInBatches now create
  Tag nodes with TAGGED_WITH and REFERENCES relationships
- Update delete.go: CheckForDeleted uses Tag traversal for kind 5 detection
- Add v3 migration in migrations.go to convert existing direct REFERENCES
  and MENTIONS relationships to the new Tag-based model
- Create comprehensive test file tag_model_test.go with 15+ test functions
  covering Tag model, filter queries, migrations, and deletion detection
- Update save-event_test.go to verify new Tag-based relationship patterns
- Update WOT_SPEC.md with Tag-Based References documentation section
- Update CLAUDE.md and README.md with Neo4j Tag-based model documentation
- Bump version to v0.36.0

This change enables #e and #p filter queries to work correctly by storing
all tags (including e/p) through intermediate Tag nodes.

Files modified:
- pkg/neo4j/save-event.go: Tag-based e/p relationship creation
- pkg/neo4j/delete.go: Tag traversal for deletion detection
- pkg/neo4j/migrations.go: v3 migration for existing data
- pkg/neo4j/tag_model_test.go: New comprehensive test file
- pkg/neo4j/save-event_test.go: Updated for new model
- pkg/neo4j/WOT_SPEC.md: Tag-Based References documentation
- pkg/neo4j/README.md: Architecture and example queries
- CLAUDE.md: Repository documentation update
- pkg/version/version: Bump to v0.36.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-16 09:22:05 +01:00
parent 516ce9c42c
commit 96bdf5cba2
9 changed files with 1457 additions and 58 deletions

View File

@@ -238,7 +238,8 @@ func (n *N) addTagsInBatches(c context.Context, eventID string, ev *event.E) err
}
// addPTagsInBatches adds p-tag (pubkey mention) relationships using UNWIND for efficiency.
// Creates NostrUser nodes for mentioned pubkeys and MENTIONS relationships.
// Creates Tag nodes with type='p' and REFERENCES relationships to NostrUser nodes.
// This enables unified tag querying via #p filters while maintaining the social graph.
func (n *N) addPTagsInBatches(c context.Context, eventID string, pTags []string) error {
// Process in batches to avoid memory issues
for i := 0; i < len(pTags); i += tagBatchSize {
@@ -249,12 +250,17 @@ func (n *N) addPTagsInBatches(c context.Context, eventID string, pTags []string)
batch := pTags[i:end]
// Use UNWIND to process multiple p-tags in a single query
// Creates Tag nodes as intermediaries, enabling unified #p filter queries
// Tag-[:REFERENCES]->NostrUser allows graph traversal from tag to user
cypher := `
MATCH (e:Event {id: $eventId})
UNWIND $pubkeys AS pubkey
MERGE (t:Tag {type: 'p', value: pubkey})
CREATE (e)-[:TAGGED_WITH]->(t)
WITH t, pubkey
MERGE (u:NostrUser {pubkey: pubkey})
ON CREATE SET u.created_at = timestamp()
CREATE (e)-[:MENTIONS]->(u)`
MERGE (t)-[:REFERENCES]->(u)`
params := map[string]any{
"eventId": eventID,
@@ -270,7 +276,8 @@ CREATE (e)-[:MENTIONS]->(u)`
}
// addETagsInBatches adds e-tag (event reference) relationships using UNWIND for efficiency.
// Only creates REFERENCES relationships if the referenced event exists.
// Creates Tag nodes with type='e' and REFERENCES relationships to Event nodes (if they exist).
// This enables unified tag querying via #e filters while maintaining event graph structure.
func (n *N) addETagsInBatches(c context.Context, eventID string, eTags []string) error {
// Process in batches to avoid memory issues
for i := 0; i < len(eTags); i += tagBatchSize {
@@ -281,14 +288,18 @@ func (n *N) addETagsInBatches(c context.Context, eventID string, eTags []string)
batch := eTags[i:end]
// Use UNWIND to process multiple e-tags in a single query
// OPTIONAL MATCH ensures we only create relationships if referenced event exists
// Creates Tag nodes as intermediaries, enabling unified #e filter queries
// Tag-[:REFERENCES]->Event allows graph traversal from tag to referenced event
// OPTIONAL MATCH ensures we only create REFERENCES if referenced event exists
cypher := `
MATCH (e:Event {id: $eventId})
UNWIND $eventIds AS refId
MERGE (t:Tag {type: 'e', value: refId})
CREATE (e)-[:TAGGED_WITH]->(t)
WITH t, refId
OPTIONAL MATCH (ref:Event {id: refId})
WITH e, ref
WHERE ref IS NOT NULL
CREATE (e)-[:REFERENCES]->(ref)`
MERGE (t)-[:REFERENCES]->(ref)`
params := map[string]any{
"eventId": eventID,