From 016e97925a8719a3ca1eb565c8ec003bbe7bb6c7 Mon Sep 17 00:00:00 2001 From: mleku Date: Tue, 2 Dec 2025 13:30:50 +0000 Subject: [PATCH] Refactor database configuration to use centralized struct Replaced individual environment variable access with a unified `DatabaseConfig` struct for all database backends. This centralizes configuration management, reduces redundant code, and ensures all options are documented in `app/config/config.go`. Backward compatibility is maintained with default values and retained constructors. --- CLAUDE.md | 20 ++++++-- app/config/config.go | 43 +++++++++++++++- docs/NEO4J_BACKEND.md | 4 ++ main.go | 33 ++++++++++-- pkg/database/database.go | 100 ++++++++++++++++++++++--------------- pkg/database/factory.go | 67 +++++++++++++++++++++---- pkg/database/save-event.go | 13 ++--- pkg/dgraph/dgraph.go | 48 ++++++++++-------- pkg/neo4j/README.md | 4 ++ pkg/neo4j/neo4j.go | 52 ++++++++++--------- pkg/version/version | 2 +- 11 files changed, 273 insertions(+), 113 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fc983bf..61a5d28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -139,9 +139,16 @@ export ORLY_SPROCKET_ENABLED=true # Enable policy system export ORLY_POLICY_ENABLED=true -# Database backend selection (badger or dgraph) +# Database backend selection (badger, dgraph, or neo4j) export ORLY_DB_TYPE=badger -export ORLY_DGRAPH_URL=localhost:9080 # Only for dgraph backend + +# DGraph configuration (only when ORLY_DB_TYPE=dgraph) +export ORLY_DGRAPH_URL=localhost:9080 + +# Neo4j configuration (only when ORLY_DB_TYPE=neo4j) +export ORLY_NEO4J_URI=bolt://localhost:7687 +export ORLY_NEO4J_USER=neo4j +export ORLY_NEO4J_PASSWORD=password # Query cache configuration (improves REQ response times) export ORLY_QUERY_CACHE_SIZE_MB=512 # Default: 512MB @@ -150,6 +157,7 @@ export ORLY_QUERY_CACHE_MAX_AGE=5m # Cache expiry time # Database cache tuning (for Badger backend) export ORLY_DB_BLOCK_CACHE_MB=512 # Block cache size export ORLY_DB_INDEX_CACHE_MB=256 # Index cache size +export ORLY_INLINE_EVENT_THRESHOLD=1024 # Inline storage threshold (bytes) ``` ## Code Architecture @@ -299,9 +307,15 @@ export ORLY_DB_INDEX_CACHE_MB=256 # Index cache size **Configuration System:** - Uses `go-simpler.org/env` for struct tags -- All config in `app/config/config.go` with `ORLY_` prefix +- **IMPORTANT: ALL environment variables MUST be defined in `app/config/config.go`** + - Never use `os.Getenv()` directly in packages - always pass config via structs + - This ensures all config options appear in `./orly help` output + - Database backends receive config via `database.DatabaseConfig` struct + - Use `GetDatabaseConfigValues()` helper to extract DB config from app config +- All config fields use `ORLY_` prefix with struct tags defining defaults and usage - Supports XDG directories via `github.com/adrg/xdg` - Default data directory: `~/.local/share/ORLY` +- Database-specific config (Neo4j, DGraph, Badger) is passed via `DatabaseConfig` struct in `pkg/database/factory.go` **Event Publishing:** - `pkg/protocol/publish/` manages publisher registry diff --git a/app/config/config.go b/app/config/config.go index 8d6ba22..00390e5 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -1,5 +1,13 @@ // Package config provides a go-simpler.org/env configuration table and helpers // for working with the list of key/value lists stored in .env files. +// +// IMPORTANT: This file is the SINGLE SOURCE OF TRUTH for all environment variables. +// All configuration options MUST be defined here with proper `env` struct tags. +// Never use os.Getenv() directly in other packages - pass configuration via structs. +// This ensures all options appear in `./orly help` output and are documented. +// +// For database backends, use GetDatabaseConfigValues() to extract database-specific +// settings, then construct a database.DatabaseConfig in the caller (e.g., main.go). package config import ( @@ -82,11 +90,19 @@ type C struct { NIP43InviteExpiry time.Duration `env:"ORLY_NIP43_INVITE_EXPIRY" default:"24h" usage:"how long invite codes remain valid"` // Database configuration - DBType string `env:"ORLY_DB_TYPE" default:"badger" usage:"database backend to use: badger or dgraph"` + DBType string `env:"ORLY_DB_TYPE" default:"badger" usage:"database backend to use: badger, dgraph, or neo4j"` DgraphURL string `env:"ORLY_DGRAPH_URL" default:"localhost:9080" usage:"dgraph gRPC endpoint address (only used when ORLY_DB_TYPE=dgraph)"` QueryCacheSizeMB int `env:"ORLY_QUERY_CACHE_SIZE_MB" default:"512" usage:"query cache size in MB (caches database query results for faster REQ responses)"` QueryCacheMaxAge string `env:"ORLY_QUERY_CACHE_MAX_AGE" default:"5m" usage:"maximum age for cached query results (e.g., 5m, 10m, 1h)"` + // Neo4j configuration (only used when ORLY_DB_TYPE=neo4j) + Neo4jURI string `env:"ORLY_NEO4J_URI" default:"bolt://localhost:7687" usage:"Neo4j bolt URI (only used when ORLY_DB_TYPE=neo4j)"` + Neo4jUser string `env:"ORLY_NEO4J_USER" default:"neo4j" usage:"Neo4j authentication username (only used when ORLY_DB_TYPE=neo4j)"` + Neo4jPassword string `env:"ORLY_NEO4J_PASSWORD" default:"password" usage:"Neo4j authentication password (only used when ORLY_DB_TYPE=neo4j)"` + + // Advanced database tuning + InlineEventThreshold int `env:"ORLY_INLINE_EVENT_THRESHOLD" default:"1024" usage:"size threshold in bytes for inline event storage in Badger (0 to disable, typical values: 384-1024)"` + // TLS configuration TLSDomains []string `env:"ORLY_TLS_DOMAINS" usage:"comma-separated list of domains to respond to for TLS"` Certs []string `env:"ORLY_CERTS" usage:"comma-separated list of paths to certificate root names (e.g., /path/to/cert will load /path/to/cert.pem and /path/to/cert.key)"` @@ -369,3 +385,28 @@ func PrintHelp(cfg *C, printer io.Writer) { PrintEnv(cfg, printer) fmt.Fprintln(printer) } + +// GetDatabaseConfigValues returns the database configuration values as individual fields. +// This avoids circular imports with pkg/database while allowing main.go to construct +// a database.DatabaseConfig with the correct type. +func (cfg *C) GetDatabaseConfigValues() ( + dataDir, logLevel string, + blockCacheMB, indexCacheMB, queryCacheSizeMB int, + queryCacheMaxAge time.Duration, + inlineEventThreshold int, + dgraphURL, neo4jURI, neo4jUser, neo4jPassword string, +) { + // Parse query cache max age from string to duration + queryCacheMaxAge = 5 * time.Minute // Default + if cfg.QueryCacheMaxAge != "" { + if duration, err := time.ParseDuration(cfg.QueryCacheMaxAge); err == nil { + queryCacheMaxAge = duration + } + } + + return cfg.DataDir, cfg.DBLogLevel, + cfg.DBBlockCacheMB, cfg.DBIndexCacheMB, cfg.QueryCacheSizeMB, + queryCacheMaxAge, + cfg.InlineEventThreshold, + cfg.DgraphURL, cfg.Neo4jURI, cfg.Neo4jUser, cfg.Neo4jPassword +} diff --git a/docs/NEO4J_BACKEND.md b/docs/NEO4J_BACKEND.md index 8d9d8f1..3310d4f 100644 --- a/docs/NEO4J_BACKEND.md +++ b/docs/NEO4J_BACKEND.md @@ -177,6 +177,10 @@ LIMIT $limit ## Configuration +All configuration is centralized in `app/config/config.go` and visible via `./orly help`. + +> **Important:** All environment variables must be defined in `app/config/config.go`. Do not use `os.Getenv()` directly in package code. Database backends receive configuration via the `database.DatabaseConfig` struct. + ### Environment Variables ```bash diff --git a/main.go b/main.go index 0cd98fb..ea15feb 100644 --- a/main.go +++ b/main.go @@ -42,8 +42,8 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() var db database.Database - if db, err = database.NewDatabase( - ctx, cancel, cfg.DBType, cfg.DataDir, cfg.DBLogLevel, + if db, err = database.NewDatabaseWithConfig( + ctx, cancel, cfg.DBType, makeDatabaseConfig(cfg), ); chk.E(err) { os.Exit(1) } @@ -318,8 +318,8 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) var db database.Database log.I.F("initializing %s database at %s", cfg.DBType, cfg.DataDir) - if db, err = database.NewDatabase( - ctx, cancel, cfg.DBType, cfg.DataDir, cfg.DBLogLevel, + if db, err = database.NewDatabaseWithConfig( + ctx, cancel, cfg.DBType, makeDatabaseConfig(cfg), ); chk.E(err) { os.Exit(1) } @@ -430,3 +430,28 @@ func main() { } // log.I.F("exiting") } + +// makeDatabaseConfig creates a database.DatabaseConfig from the app config. +// This helper function extracts all database-specific configuration values +// and constructs the appropriate struct for the database package. +func makeDatabaseConfig(cfg *config.C) *database.DatabaseConfig { + dataDir, logLevel, + blockCacheMB, indexCacheMB, queryCacheSizeMB, + queryCacheMaxAge, + inlineEventThreshold, + dgraphURL, neo4jURI, neo4jUser, neo4jPassword := cfg.GetDatabaseConfigValues() + + return &database.DatabaseConfig{ + DataDir: dataDir, + LogLevel: logLevel, + BlockCacheMB: blockCacheMB, + IndexCacheMB: indexCacheMB, + QueryCacheSizeMB: queryCacheSizeMB, + QueryCacheMaxAge: queryCacheMaxAge, + InlineEventThreshold: inlineEventThreshold, + DgraphURL: dgraphURL, + Neo4jURI: neo4jURI, + Neo4jUser: neo4jUser, + Neo4jPassword: neo4jPassword, + } +} diff --git a/pkg/database/database.go b/pkg/database/database.go index 2540315..bee83d4 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -5,7 +5,6 @@ import ( "errors" "os" "path/filepath" - "strconv" "time" "github.com/dgraph-io/badger/v4" @@ -21,10 +20,11 @@ import ( // D implements the Database interface using Badger as the storage backend type D struct { - ctx context.Context - cancel context.CancelFunc - dataDir string - Logger *logger + ctx context.Context + cancel context.CancelFunc + dataDir string + Logger *logger + inlineEventThreshold int // Configurable threshold for inline event storage *badger.DB seq *badger.Sequence pubkeySeq *badger.Sequence // Sequence for pubkey serials @@ -35,63 +35,85 @@ type D struct { // Ensure D implements Database interface at compile time var _ Database = (*D)(nil) +// New creates a new Badger database instance with default configuration. +// This is provided for backward compatibility with existing callers. +// For full configuration control, use NewWithConfig instead. func New( ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, ) ( d *D, err error, ) { - // Initialize query cache with configurable size (default 512MB) - queryCacheSize := int64(512 * 1024 * 1024) // 512 MB - if v := os.Getenv("ORLY_QUERY_CACHE_SIZE_MB"); v != "" { - if n, perr := strconv.Atoi(v); perr == nil && n > 0 { - queryCacheSize = int64(n * 1024 * 1024) - } + // Create a default config for backward compatibility + cfg := &DatabaseConfig{ + DataDir: dataDir, + LogLevel: logLevel, + BlockCacheMB: 1024, // Default 1024 MB + IndexCacheMB: 512, // Default 512 MB + QueryCacheSizeMB: 512, // Default 512 MB + QueryCacheMaxAge: 5 * time.Minute, // Default 5 minutes + InlineEventThreshold: 1024, // Default 1024 bytes } - queryCacheMaxAge := 5 * time.Minute // Default 5 minutes - if v := os.Getenv("ORLY_QUERY_CACHE_MAX_AGE"); v != "" { - if duration, perr := time.ParseDuration(v); perr == nil { - queryCacheMaxAge = duration - } + return NewWithConfig(ctx, cancel, cfg) +} + +// NewWithConfig creates a new Badger database instance with full configuration. +// This is the preferred method when you have access to DatabaseConfig. +func NewWithConfig( + ctx context.Context, cancel context.CancelFunc, cfg *DatabaseConfig, +) ( + d *D, err error, +) { + // Apply defaults for zero values (backward compatibility) + blockCacheMB := cfg.BlockCacheMB + if blockCacheMB == 0 { + blockCacheMB = 1024 // Default 1024 MB + } + indexCacheMB := cfg.IndexCacheMB + if indexCacheMB == 0 { + indexCacheMB = 512 // Default 512 MB + } + queryCacheSizeMB := cfg.QueryCacheSizeMB + if queryCacheSizeMB == 0 { + queryCacheSizeMB = 512 // Default 512 MB + } + queryCacheMaxAge := cfg.QueryCacheMaxAge + if queryCacheMaxAge == 0 { + queryCacheMaxAge = 5 * time.Minute // Default 5 minutes + } + inlineEventThreshold := cfg.InlineEventThreshold + if inlineEventThreshold == 0 { + inlineEventThreshold = 1024 // Default 1024 bytes } + queryCacheSize := int64(queryCacheSizeMB * 1024 * 1024) + d = &D{ - ctx: ctx, - cancel: cancel, - dataDir: dataDir, - Logger: NewLogger(lol.GetLogLevel(logLevel), dataDir), - DB: nil, - seq: nil, - ready: make(chan struct{}), - queryCache: querycache.NewEventCache(queryCacheSize, queryCacheMaxAge), + ctx: ctx, + cancel: cancel, + dataDir: cfg.DataDir, + Logger: NewLogger(lol.GetLogLevel(cfg.LogLevel), cfg.DataDir), + inlineEventThreshold: inlineEventThreshold, + DB: nil, + seq: nil, + ready: make(chan struct{}), + queryCache: querycache.NewEventCache(queryCacheSize, queryCacheMaxAge), } // Ensure the data directory exists - if err = os.MkdirAll(dataDir, 0755); chk.E(err) { + if err = os.MkdirAll(cfg.DataDir, 0755); chk.E(err) { return } // Also ensure the directory exists using apputil.EnsureDir for any // potential subdirectories - dummyFile := filepath.Join(dataDir, "dummy.sst") + dummyFile := filepath.Join(cfg.DataDir, "dummy.sst") if err = apputil.EnsureDir(dummyFile); chk.E(err) { return } opts := badger.DefaultOptions(d.dataDir) - // Configure caches based on environment to better match workload. + // Configure caches based on config to better match workload. // Defaults aim for higher hit ratios under read-heavy workloads while remaining safe. - var blockCacheMB = 1024 // default 512 MB - var indexCacheMB = 512 // default 256 MB - if v := os.Getenv("ORLY_DB_BLOCK_CACHE_MB"); v != "" { - if n, perr := strconv.Atoi(v); perr == nil && n > 0 { - blockCacheMB = n - } - } - if v := os.Getenv("ORLY_DB_INDEX_CACHE_MB"); v != "" { - if n, perr := strconv.Atoi(v); perr == nil && n > 0 { - indexCacheMB = n - } - } opts.BlockCacheSize = int64(blockCacheMB * units.Mb) opts.IndexCacheSize = int64(indexCacheMB * units.Mb) opts.BlockSize = 4 * units.Kb // 4 KB block size diff --git a/pkg/database/factory.go b/pkg/database/factory.go index cd5f5ad..8f76b4f 100644 --- a/pkg/database/factory.go +++ b/pkg/database/factory.go @@ -4,8 +4,33 @@ import ( "context" "fmt" "strings" + "time" ) +// DatabaseConfig holds all database configuration options that can be passed +// to any database backend. Each backend uses the relevant fields for its type. +// This centralizes configuration instead of having each backend read env vars directly. +type DatabaseConfig struct { + // Common settings for all backends + DataDir string + LogLevel string + + // Badger-specific settings + BlockCacheMB int // ORLY_DB_BLOCK_CACHE_MB + IndexCacheMB int // ORLY_DB_INDEX_CACHE_MB + QueryCacheSizeMB int // ORLY_QUERY_CACHE_SIZE_MB + QueryCacheMaxAge time.Duration // ORLY_QUERY_CACHE_MAX_AGE + InlineEventThreshold int // ORLY_INLINE_EVENT_THRESHOLD + + // DGraph-specific settings + DgraphURL string // ORLY_DGRAPH_URL + + // Neo4j-specific settings + Neo4jURI string // ORLY_NEO4J_URI + Neo4jUser string // ORLY_NEO4J_USER + Neo4jPassword string // ORLY_NEO4J_PASSWORD +} + // NewDatabase creates a database instance based on the specified type. // Supported types: "badger", "dgraph", "neo4j" func NewDatabase( @@ -14,19 +39,39 @@ func NewDatabase( dbType string, dataDir string, logLevel string, +) (Database, error) { + // Create a default config for backward compatibility with existing callers + cfg := &DatabaseConfig{ + DataDir: dataDir, + LogLevel: logLevel, + } + return NewDatabaseWithConfig(ctx, cancel, dbType, cfg) +} + +// NewDatabaseWithConfig creates a database instance with full configuration. +// This is the preferred method when you have access to the app config. +func NewDatabaseWithConfig( + ctx context.Context, + cancel context.CancelFunc, + dbType string, + cfg *DatabaseConfig, ) (Database, error) { switch strings.ToLower(dbType) { case "badger", "": // Use the existing badger implementation - return New(ctx, cancel, dataDir, logLevel) + return NewWithConfig(ctx, cancel, cfg) case "dgraph": - // Use the new dgraph implementation - // Import dynamically to avoid import cycles - return newDgraphDatabase(ctx, cancel, dataDir, logLevel) + // Use the dgraph implementation + if newDgraphDatabase == nil { + return nil, fmt.Errorf("dgraph database backend not available (import _ \"next.orly.dev/pkg/dgraph\")") + } + return newDgraphDatabase(ctx, cancel, cfg) case "neo4j": - // Use the new neo4j implementation - // Import dynamically to avoid import cycles - return newNeo4jDatabase(ctx, cancel, dataDir, logLevel) + // Use the neo4j implementation + if newNeo4jDatabase == nil { + return nil, fmt.Errorf("neo4j database backend not available (import _ \"next.orly.dev/pkg/neo4j\")") + } + return newNeo4jDatabase(ctx, cancel, cfg) default: return nil, fmt.Errorf("unsupported database type: %s (supported: badger, dgraph, neo4j)", dbType) } @@ -34,20 +79,20 @@ func NewDatabase( // newDgraphDatabase creates a dgraph database instance // This is defined here to avoid import cycles -var newDgraphDatabase func(context.Context, context.CancelFunc, string, string) (Database, error) +var newDgraphDatabase func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error) // RegisterDgraphFactory registers the dgraph database factory // This is called from the dgraph package's init() function -func RegisterDgraphFactory(factory func(context.Context, context.CancelFunc, string, string) (Database, error)) { +func RegisterDgraphFactory(factory func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)) { newDgraphDatabase = factory } // newNeo4jDatabase creates a neo4j database instance // This is defined here to avoid import cycles -var newNeo4jDatabase func(context.Context, context.CancelFunc, string, string) (Database, error) +var newNeo4jDatabase func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error) // RegisterNeo4jFactory registers the neo4j database factory // This is called from the neo4j package's init() function -func RegisterNeo4jFactory(factory func(context.Context, context.CancelFunc, string, string) (Database, error)) { +func RegisterNeo4jFactory(factory func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)) { newNeo4jDatabase = factory } diff --git a/pkg/database/save-event.go b/pkg/database/save-event.go index 19836c2..61a94aa 100644 --- a/pkg/database/save-event.go +++ b/pkg/database/save-event.go @@ -5,8 +5,6 @@ import ( "context" "errors" "fmt" - "os" - "strconv" "strings" "github.com/dgraph-io/badger/v4" @@ -270,14 +268,9 @@ func (d *D) SaveEvent(c context.Context, ev *event.E) ( eventData := eventDataBuf.Bytes() // Determine storage strategy (Reiser4 optimizations) - // Get threshold from environment, default to 0 (disabled) - // When enabled, typical values: 384 (conservative), 512 (recommended), 1024 (aggressive) - smallEventThreshold := 1024 - if v := os.Getenv("ORLY_INLINE_EVENT_THRESHOLD"); v != "" { - if n, perr := strconv.Atoi(v); perr == nil && n >= 0 { - smallEventThreshold = n - } - } + // Use the threshold from database configuration + // Typical values: 384 (conservative), 512 (recommended), 1024 (aggressive) + smallEventThreshold := d.inlineEventThreshold isSmallEvent := smallEventThreshold > 0 && len(eventData) <= smallEventThreshold isReplaceableEvent := kind.IsReplaceable(ev.Kind) isAddressableEvent := kind.IsParameterizedReplaceable(ev.Kind) diff --git a/pkg/dgraph/dgraph.go b/pkg/dgraph/dgraph.go index 3f15b5b..150c094 100644 --- a/pkg/dgraph/dgraph.go +++ b/pkg/dgraph/dgraph.go @@ -48,30 +48,21 @@ func init() { database.RegisterDgraphFactory(func( ctx context.Context, cancel context.CancelFunc, - dataDir string, - logLevel string, + cfg *database.DatabaseConfig, ) (database.Database, error) { - return New(ctx, cancel, dataDir, logLevel) + return NewWithConfig(ctx, cancel, cfg) }) } -// Config holds configuration options for the Dgraph database -type Config struct { - DataDir string - LogLevel string - DgraphURL string // Dgraph gRPC endpoint (e.g., "localhost:9080") - EnableGraphQL bool - EnableIntrospection bool -} - -// New creates a new Dgraph-based database instance -func New( - ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, +// NewWithConfig creates a new Dgraph-based database instance with full configuration. +// Configuration is passed from the centralized app config via DatabaseConfig. +func NewWithConfig( + ctx context.Context, cancel context.CancelFunc, cfg *database.DatabaseConfig, ) ( d *D, err error, ) { - // Get dgraph URL from environment, default to localhost - dgraphURL := os.Getenv("ORLY_DGRAPH_URL") + // Apply defaults for empty values + dgraphURL := cfg.DgraphURL if dgraphURL == "" { dgraphURL = "localhost:9080" } @@ -79,8 +70,8 @@ func New( d = &D{ ctx: ctx, cancel: cancel, - dataDir: dataDir, - Logger: NewLogger(lol.GetLogLevel(logLevel), dataDir), + dataDir: cfg.DataDir, + Logger: NewLogger(lol.GetLogLevel(cfg.LogLevel), cfg.DataDir), dgraphURL: dgraphURL, enableGraphQL: false, enableIntrospection: false, @@ -88,12 +79,12 @@ func New( } // Ensure the data directory exists - if err = os.MkdirAll(dataDir, 0755); chk.E(err) { + if err = os.MkdirAll(cfg.DataDir, 0755); chk.E(err) { return } // Ensure directory structure - dummyFile := filepath.Join(dataDir, "dummy.sst") + dummyFile := filepath.Join(cfg.DataDir, "dummy.sst") if err = apputil.EnsureDir(dummyFile); chk.E(err) { return } @@ -128,6 +119,21 @@ func New( return } +// New creates a new Dgraph-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 New( + ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, +) ( + d *D, err error, +) { + cfg := &database.DatabaseConfig{ + DataDir: dataDir, + LogLevel: logLevel, + } + return NewWithConfig(ctx, cancel, cfg) +} + // initDgraphClient establishes connection to dgraph server func (d *D) initDgraphClient() error { d.Logger.Infof("connecting to dgraph at %s", d.dgraphURL) diff --git a/pkg/neo4j/README.md b/pkg/neo4j/README.md index 75d5b0d..03b92ae 100644 --- a/pkg/neo4j/README.md +++ b/pkg/neo4j/README.md @@ -15,6 +15,8 @@ docker run -d --name neo4j \ ### 2. Configure Environment +All Neo4j configuration is defined in `app/config/config.go` and visible via `./orly help`: + ```bash export ORLY_DB_TYPE=neo4j export ORLY_NEO4J_URI=bolt://localhost:7687 @@ -22,6 +24,8 @@ 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 ```bash diff --git a/pkg/neo4j/neo4j.go b/pkg/neo4j/neo4j.go index ebe7304..d38d093 100644 --- a/pkg/neo4j/neo4j.go +++ b/pkg/neo4j/neo4j.go @@ -70,38 +70,29 @@ func init() { database.RegisterNeo4jFactory(func( ctx context.Context, cancel context.CancelFunc, - dataDir string, - logLevel string, + cfg *database.DatabaseConfig, ) (database.Database, error) { - return New(ctx, cancel, dataDir, logLevel) + return NewWithConfig(ctx, cancel, cfg) }) } -// Config holds configuration options for the Neo4j database -type Config struct { - DataDir string - LogLevel string - Neo4jURI string // Neo4j bolt URI (e.g., "bolt://localhost:7687") - Neo4jUser string // Authentication username - Neo4jPassword string // Authentication password -} - -// New creates a new Neo4j-based database instance -func New( - ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, +// NewWithConfig creates a new Neo4j-based database instance with full configuration. +// Configuration is passed from the centralized app config via DatabaseConfig. +func NewWithConfig( + ctx context.Context, cancel context.CancelFunc, cfg *database.DatabaseConfig, ) ( n *N, err error, ) { - // Get Neo4j connection details from environment - neo4jURI := os.Getenv("ORLY_NEO4J_URI") + // Apply defaults for empty values + neo4jURI := cfg.Neo4jURI if neo4jURI == "" { neo4jURI = "bolt://localhost:7687" } - neo4jUser := os.Getenv("ORLY_NEO4J_USER") + neo4jUser := cfg.Neo4jUser if neo4jUser == "" { neo4jUser = "neo4j" } - neo4jPassword := os.Getenv("ORLY_NEO4J_PASSWORD") + neo4jPassword := cfg.Neo4jPassword if neo4jPassword == "" { neo4jPassword = "password" } @@ -109,8 +100,8 @@ func New( n = &N{ ctx: ctx, cancel: cancel, - dataDir: dataDir, - Logger: NewLogger(lol.GetLogLevel(logLevel), dataDir), + dataDir: cfg.DataDir, + Logger: NewLogger(lol.GetLogLevel(cfg.LogLevel), cfg.DataDir), neo4jURI: neo4jURI, neo4jUser: neo4jUser, neo4jPassword: neo4jPassword, @@ -118,12 +109,12 @@ func New( } // Ensure the data directory exists - if err = os.MkdirAll(dataDir, 0755); chk.E(err) { + if err = os.MkdirAll(cfg.DataDir, 0755); chk.E(err) { return } // Ensure directory structure - dummyFile := filepath.Join(dataDir, "dummy.sst") + dummyFile := filepath.Join(cfg.DataDir, "dummy.sst") if err = apputil.EnsureDir(dummyFile); chk.E(err) { return } @@ -158,6 +149,21 @@ func New( return } +// 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 New( + ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, +) ( + n *N, err error, +) { + cfg := &database.DatabaseConfig{ + DataDir: dataDir, + LogLevel: logLevel, + } + return NewWithConfig(ctx, cancel, cfg) +} + // initNeo4jClient establishes connection to Neo4j server func (n *N) initNeo4jClient() error { n.Logger.Infof("connecting to neo4j at %s", n.neo4jURI) diff --git a/pkg/version/version b/pkg/version/version index c580c49..e6ad33a 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.31.7 +v0.31.8 \ No newline at end of file