mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools
Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
* GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
* GET /api/v1/users/me/organizations - User organizations
* POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue
API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules
Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
12 KiB
12 KiB
Neo4j Go Driver Development Guide
Library: github.com/neo4j/neo4j-go-driver/v5
Used In: MVP - Graph database for resource matching
Purpose: Connect to Neo4j and execute Cypher queries
Where It's Used
- Store graph structure (Business → Site → ResourceFlow)
- Query matches between ResourceFlows
- Create and manage relationships
- Graph traversal for matching algorithm
Official Documentation
- GitHub: https://github.com/neo4j/neo4j-go-driver
- Official Docs: https://neo4j.com/docs/driver-manual/current/drivers/go/
- GoDoc: https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5
- Cypher Query Language: https://neo4j.com/docs/cypher-manual/current/
Installation
go get github.com/neo4j/neo4j-go-driver/v5
Key Concepts
1. Driver Initialization
import (
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"context"
)
func NewNeo4jDriver(uri, username, password string) (neo4j.DriverWithContext, error) {
driver, err := neo4j.NewDriverWithContext(
uri, // e.g., "neo4j://localhost:7687"
neo4j.BasicAuth(username, password, ""),
)
if err != nil {
return nil, err
}
// Verify connectivity
ctx := context.Background()
if err := driver.VerifyConnectivity(ctx); err != nil {
driver.Close(ctx)
return nil, err
}
return driver, nil
}
2. Session Management
// Create session
session := driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite, // or AccessModeRead
DatabaseName: "neo4j", // or your database name
})
defer session.Close(ctx)
// Execute query
result, err := session.Run(ctx, cypher, params)
3. Basic Cypher Queries
// Create node
cypher := "CREATE (b:Business {id: $id, name: $name, email: $email}) RETURN b"
params := map[string]interface{}{
"id": uuid.New().String(),
"name": "Factory A",
"email": "contact@factorya.com",
}
result, err := session.Run(ctx, cypher, params)
if err != nil {
return err
}
// Process single record
record, err := result.Single(ctx)
if err != nil {
return err
}
node, _ := record.Get("b")
businessNode := node.(neo4j.Node)
4. Create Relationships
// Match nodes and create relationship
cypher := `
MATCH (b:Business {id: $businessID})
MATCH (s:Site {id: $siteID})
CREATE (b)-[:OPERATES_AT]->(s)
RETURN b, s
`
params := map[string]interface{}{
"businessID": businessID,
"siteID": siteID,
}
_, err := session.Run(ctx, cypher, params)
5. Query with Results
// Query multiple records
cypher := `
MATCH (b:Business)-[:OPERATES_AT]->(s:Site)
WHERE b.id = $businessID
RETURN b, s
`
params := map[string]interface{}{
"businessID": businessID,
}
result, err := session.Run(ctx, cypher, params)
if err != nil {
return err
}
var sites []Site
for result.Next(ctx) {
record := result.Record()
siteNode, _ := record.Get("s")
site := parseSiteNode(siteNode.(neo4j.Node))
sites = append(sites, site)
}
if err := result.Err(); err != nil {
return err
}
6. Transactions
// Transaction with automatic commit/rollback
_, err = session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
// Create business
result, err := tx.Run(ctx,
"CREATE (b:Business {id: $id, name: $name}) RETURN b.id",
map[string]interface{}{"id": id, "name": name},
)
if err != nil {
return nil, err
}
record, err := result.Single(ctx)
if err != nil {
return nil, err
}
businessID, _ := record.Get("b.id")
// Create site in same transaction
_, err = tx.Run(ctx,
"MATCH (b:Business {id: $businessID}) CREATE (s:Site {id: $siteID}) CREATE (b)-[:OPERATES_AT]->(s)",
map[string]interface{}{"businessID": businessID, "siteID": siteID},
)
return businessID, err
})
7. Parameter Binding
// Using struct for parameters
type BusinessParams struct {
ID string
Name string
Email string
}
params := BusinessParams{
ID: uuid.New().String(),
Name: "Factory A",
Email: "contact@factorya.com",
}
cypher := `
CREATE (b:Business {
id: $id,
name: $name,
email: $email
})
RETURN b
`
// Neo4j driver automatically converts struct to map
result, err := session.Run(ctx, cypher, params)
8. Extracting Values from Records
record, _ := result.Single(ctx)
// Get by key
node, _ := record.Get("b")
relationship, _ := record.Get("r")
// Type assertions
businessNode := node.(neo4j.Node)
props := businessNode.Props
// Extract properties
id, _ := props["id"].(string)
name, _ := props["name"].(string)
// Or use helper function
func getString(record neo4j.Record, key string) string {
val, ok := record.Get(key)
if !ok {
return ""
}
str, _ := val.(string)
return str
}
MVP-Specific Patterns
Resource Flow Service
type Neo4jService struct {
driver neo4j.DriverWithContext
}
func (s *Neo4jService) CreateResourceFlow(ctx context.Context, flow ResourceFlow) error {
session := s.driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
})
defer session.Close(ctx)
cypher := `
MATCH (site:Site {id: $siteID})
CREATE (flow:ResourceFlow {
id: $id,
direction: $direction,
type: $type,
temperature_celsius: $temperature,
quantity_kwh_per_month: $quantity,
cost_per_kwh_euro: $cost
})
CREATE (site)-[:HOSTS]->(flow)
RETURN flow.id
`
params := map[string]interface{}{
"id": flow.ID,
"siteID": flow.SiteID,
"direction": flow.Direction,
"type": flow.Type,
"temperature": flow.TemperatureCelsius,
"quantity": flow.QuantityKwhPerMonth,
"cost": flow.CostPerKwhEuro,
}
_, err := session.Run(ctx, cypher, params)
return err
}
Matching Query
func (s *Neo4jService) FindMatches(ctx context.Context, flowID string, maxDistanceKm float64) ([]Match, error) {
session := s.driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
})
defer session.Close(ctx)
cypher := `
MATCH (sourceFlow:ResourceFlow {id: $flowID})-[:HOSTS]->(sourceSite:Site),
(targetFlow:ResourceFlow)-[:HOSTS]->(targetSite:Site)
WHERE sourceFlow.direction = 'output'
AND targetFlow.direction = 'input'
AND sourceFlow.type = 'heat'
AND targetFlow.type = 'heat'
AND ABS(sourceFlow.temperature_celsius - targetFlow.temperature_celsius) <= 10
WITH sourceFlow, targetFlow, sourceSite, targetSite,
point.distance(
point({longitude: sourceSite.longitude, latitude: sourceSite.latitude}),
point({longitude: targetSite.longitude, latitude: targetSite.latitude})
) / 1000 AS distance_km
WHERE distance_km <= $maxDistance
RETURN targetFlow.id AS target_flow_id,
targetFlow.temperature_celsius AS target_temp,
targetFlow.quantity_kwh_per_month AS target_quantity,
distance_km
ORDER BY distance_km ASC
LIMIT 20
`
params := map[string]interface{}{
"flowID": flowID,
"maxDistance": maxDistanceKm,
}
result, err := session.Run(ctx, cypher, params)
if err != nil {
return nil, err
}
var matches []Match
for result.Next(ctx) {
record := result.Record()
match := Match{
TargetFlowID: getString(record, "target_flow_id"),
TargetTemp: getFloat(record, "target_temp"),
TargetQuantity: getFloat(record, "target_quantity"),
DistanceKm: getFloat(record, "distance_km"),
}
matches = append(matches, match)
}
return matches, result.Err()
}
Connection Pooling
// Driver automatically manages connection pool
// Configure during driver creation
driver, err := neo4j.NewDriverWithContext(
uri,
neo4j.BasicAuth(username, password, ""),
func(config *neo4j.Config) {
config.MaxConnectionPoolSize = 50
config.ConnectionAcquisitionTimeout = 30 * time.Second
config.MaxTransactionRetryTime = 30 * time.Second
},
)
Error Handling
result, err := session.Run(ctx, cypher, params)
if err != nil {
// Check for specific Neo4j errors
if neo4jErr, ok := err.(*neo4j.Neo4jError); ok {
switch neo4jErr.Code {
case "Neo.ClientError.Statement.SyntaxError":
// Handle syntax error
case "Neo.ClientError.Security.Unauthorized":
// Handle auth error
default:
// Handle other errors
}
}
return err
}
// Check result errors
if err := result.Err(); err != nil {
return err
}
Performance Tips
- Reuse sessions - create session per request/operation, not per query
- Use transactions - batch operations in single transaction
- Parameterize queries - always use parameters, never string concatenation
- Create indexes - for frequently queried properties
- Use LIMIT - always limit query results
- Profile queries - use
EXPLAINandPROFILEto optimize Cypher
Indexes
// Create indexes for better performance
indexes := []string{
"CREATE INDEX business_id IF NOT EXISTS FOR (b:Business) ON (b.id)",
"CREATE INDEX site_id IF NOT EXISTS FOR (s:Site) ON (s.id)",
"CREATE INDEX resource_flow_direction IF NOT EXISTS FOR (r:ResourceFlow) ON (r.direction)",
"CREATE INDEX resource_flow_type IF NOT EXISTS FOR (r:ResourceFlow) ON (r.type)",
}
for _, index := range indexes {
_, err := session.Run(ctx, index, nil)
if err != nil {
log.Printf("Failed to create index: %v", err)
}
}
Tutorials & Resources
- Neo4j Go Driver Examples: https://github.com/neo4j/neo4j-go-driver/tree/5.0/examples
- Getting Started Tutorial: https://neo4j.com/developer/go/
- Cypher Manual: https://neo4j.com/docs/cypher-manual/current/
- Best Practices: https://neo4j.com/developer/go-driver/#_best_practices
Common Patterns
Repository Pattern
type BusinessRepository struct {
driver neo4j.DriverWithContext
}
func (r *BusinessRepository) FindByID(ctx context.Context, id string) (*Business, error) {
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
})
defer session.Close(ctx)
// ... query logic ...
}
func (r *BusinessRepository) Create(ctx context.Context, business *Business) error {
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
})
defer session.Close(ctx)
// ... create logic ...
}
Helper Functions
func parseBusinessNode(node neo4j.Node) *Business {
props := node.Props
return &Business{
ID: getString(props, "id"),
Name: getString(props, "name"),
Email: getString(props, "email"),
}
}
func getString(m map[string]interface{}, key string) string {
if val, ok := m[key]; ok {
if str, ok := val.(string); ok {
return str
}
}
return ""
}
func getFloat(m map[string]interface{}, key string) float64 {
if val, ok := m[key]; ok {
if f, ok := val.(float64); ok {
return f
}
}
return 0
}