Implement Tag-based e/p model for Neo4j backend (v0.36.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user