turash/bugulma/backend/pkg/database/neo4j.go
Damir Mukimov f434b26dd4
Some checks failed
CI/CD Pipeline / frontend-lint (push) Successful in 1m38s
CI/CD Pipeline / backend-lint (push) Failing after 1m41s
CI/CD Pipeline / backend-build (push) Has been skipped
CI/CD Pipeline / frontend-build (push) Failing after 26s
CI/CD Pipeline / e2e-test (push) Has been skipped
Enhance configuration management and testing for backend
- Update .gitignore to selectively ignore pkg/ directories at the root level
- Modify CI workflow to verify all Go packages can be listed
- Introduce configuration management with a new config package, including loading environment variables
- Add comprehensive tests for configuration loading and environment variable handling
- Implement Neo4j database interaction functions with corresponding tests for data extraction
2025-12-26 13:18:00 +01:00

206 lines
5.5 KiB
Go

package database
import (
"context"
"fmt"
"time"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
// Neo4jConfig holds configuration for Neo4j connection
type Neo4jConfig struct {
URI string
Username string
Password string
Database string
}
// NewNeo4jDriver creates a new Neo4j driver instance
func NewNeo4jDriver(config Neo4jConfig) (neo4j.DriverWithContext, error) {
driver, err := neo4j.NewDriverWithContext(
config.URI,
neo4j.BasicAuth(config.Username, config.Password, ""),
func(c *neo4j.Config) {
c.MaxConnectionPoolSize = 50
c.ConnectionAcquisitionTimeout = 30 * time.Second
c.MaxTransactionRetryTime = 30 * time.Second
},
)
if err != nil {
return nil, fmt.Errorf("failed to create Neo4j driver: %w", err)
}
// Verify connectivity
ctx := context.Background()
if err := driver.VerifyConnectivity(ctx); err != nil {
driver.Close(ctx)
return nil, fmt.Errorf("failed to verify Neo4j connectivity: %w", err)
}
return driver, nil
}
// InitializeSchema creates constraints and indexes in Neo4j
func InitializeSchema(ctx context.Context, driver neo4j.DriverWithContext, database string) error {
session := driver.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
DatabaseName: database,
})
defer session.Close(ctx)
// Constraints for uniqueness
constraints := []string{
"CREATE CONSTRAINT business_id_unique IF NOT EXISTS FOR (b:Business) REQUIRE b.id IS UNIQUE",
"CREATE CONSTRAINT organization_id_unique IF NOT EXISTS FOR (o:Organization) REQUIRE o.id IS UNIQUE",
"CREATE CONSTRAINT site_id_unique IF NOT EXISTS FOR (s:Site) REQUIRE s.id IS UNIQUE",
"CREATE CONSTRAINT resource_flow_id_unique IF NOT EXISTS FOR (rf:ResourceFlow) REQUIRE rf.id IS UNIQUE",
"CREATE CONSTRAINT match_id_unique IF NOT EXISTS FOR (m:Match) REQUIRE m.id IS UNIQUE",
"CREATE CONSTRAINT shared_asset_id_unique IF NOT EXISTS FOR (sa:SharedAsset) REQUIRE sa.id IS UNIQUE",
}
// Indexes for performance
indexes := []string{
"CREATE INDEX organization_name_index IF NOT EXISTS FOR (o:Organization) ON (o.name)",
"CREATE INDEX organization_sector_index IF NOT EXISTS FOR (o:Organization) ON (o.sector)",
"CREATE INDEX organization_subtype_index IF NOT EXISTS FOR (o:Organization) ON (o.subtype)",
"CREATE INDEX site_location_index IF NOT EXISTS FOR (s:Site) ON (s.latitude, s.longitude)",
"CREATE INDEX site_type_index IF NOT EXISTS FOR (s:Site) ON (s.site_type)",
"CREATE INDEX resource_flow_type_direction_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.type, rf.direction)",
"CREATE INDEX resource_flow_type_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.type)",
"CREATE INDEX resource_flow_direction_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.direction)",
"CREATE INDEX match_status_index IF NOT EXISTS FOR (m:Match) ON (m.status)",
"CREATE INDEX match_score_index IF NOT EXISTS FOR (m:Match) ON (m.compatibility_score)",
"CREATE INDEX shared_asset_type_index IF NOT EXISTS FOR (sa:SharedAsset) ON (sa.type)",
}
// Execute constraints
for _, constraint := range constraints {
if _, err := session.Run(ctx, constraint, nil); err != nil {
return fmt.Errorf("failed to create constraint: %w", err)
}
}
// Execute indexes
for _, index := range indexes {
if _, err := session.Run(ctx, index, nil); err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
}
return nil
}
// Helper functions for extracting values from Neo4j records
// GetString extracts a string value from a map
func GetString(m map[string]interface{}, key string) string {
if val, ok := m[key]; ok {
if str, ok := val.(string); ok {
return str
}
}
return ""
}
// GetFloat64 extracts a float64 value from a map
func GetFloat64(m map[string]interface{}, key string) float64 {
if val, ok := m[key]; ok {
switch v := val.(type) {
case float64:
return v
case int64:
return float64(v)
case int:
return float64(v)
}
}
return 0
}
// GetInt extracts an int value from a map
func GetInt(m map[string]interface{}, key string) int {
if val, ok := m[key]; ok {
switch v := val.(type) {
case int64:
return int(v)
case int:
return v
case float64:
return int(v)
}
}
return 0
}
// GetBool extracts a bool value from a map
func GetBool(m map[string]interface{}, key string) bool {
if val, ok := m[key]; ok {
if b, ok := val.(bool); ok {
return b
}
}
return false
}
// GetTime extracts a time.Time value from a map
func GetTime(m map[string]interface{}, key string) time.Time {
if val, ok := m[key]; ok {
if t, ok := val.(time.Time); ok {
return t
}
if str, ok := val.(string); ok {
if parsed, err := time.Parse(time.RFC3339, str); err == nil {
return parsed
}
}
}
return time.Time{}
}
// GetStringFromRecord extracts a string value from a Neo4j record
func GetStringFromRecord(record neo4j.Record, key string) string {
val, ok := record.Get(key)
if !ok {
return ""
}
if str, ok := val.(string); ok {
return str
}
return ""
}
// GetFloat64FromRecord extracts a float64 value from a Neo4j record
func GetFloat64FromRecord(record neo4j.Record, key string) float64 {
val, ok := record.Get(key)
if !ok {
return 0
}
switch v := val.(type) {
case float64:
return v
case int64:
return float64(v)
case int:
return float64(v)
}
return 0
}
// GetIntFromRecord extracts an int value from a Neo4j record
func GetIntFromRecord(record neo4j.Record, key string) int {
val, ok := record.Get(key)
if !ok {
return 0
}
switch v := val.(type) {
case int64:
return int(v)
case int:
return v
case float64:
return int(v)
}
return 0
}