feat: Implement foundation layer with domain entities and repository interfaces
- Add complete domain layer: Note, Vault, WikiLink, Tag, Frontmatter, Graph entities - Implement repository interfaces for data access abstraction - Create comprehensive configuration system with YAML and env support - Add CLI entry point with signal handling and graceful shutdown - Fix mermaid diagram syntax in design.md (array notation) - Add CLAUDE.md for development guidance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
241
internal/config/config.go
Normal file
241
internal/config/config.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Vault VaultConfig `yaml:"vault"`
|
||||
Git GitConfig `yaml:"git"`
|
||||
Index IndexConfig `yaml:"index"`
|
||||
Search SearchConfig `yaml:"search"`
|
||||
Cache CacheConfig `yaml:"cache"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
Performance PerformanceConfig `yaml:"performance"`
|
||||
Security SecurityConfig `yaml:"security"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Transport string `yaml:"transport"` // "stdio" or "sse"
|
||||
}
|
||||
|
||||
type VaultConfig struct {
|
||||
Path string `yaml:"path"`
|
||||
ExcludePaths []string `yaml:"exclude_paths"`
|
||||
WatchChanges bool `yaml:"watch_changes"`
|
||||
}
|
||||
|
||||
type GitConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
AutoPull bool `yaml:"auto_pull"`
|
||||
AutoPush bool `yaml:"auto_push"`
|
||||
AutoCommit bool `yaml:"auto_commit"`
|
||||
CommitPrefix string `yaml:"commit_prefix"`
|
||||
Remote string `yaml:"remote"`
|
||||
Branch string `yaml:"branch"`
|
||||
}
|
||||
|
||||
type IndexConfig struct {
|
||||
BuildOnStartup bool `yaml:"build_on_startup"`
|
||||
RebuildInterval time.Duration `yaml:"rebuild_interval"`
|
||||
MaxNotesInMemory int `yaml:"max_notes_in_memory"`
|
||||
}
|
||||
|
||||
type SearchConfig struct {
|
||||
MaxResults int `yaml:"max_results"`
|
||||
ContextLines int `yaml:"context_lines"`
|
||||
CaseSensitive bool `yaml:"case_sensitive"`
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
MaxSize int `yaml:"max_size"`
|
||||
TTL time.Duration `yaml:"ttl"`
|
||||
}
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level string `yaml:"level"` // debug, info, warn, error
|
||||
Format string `yaml:"format"` // text or json
|
||||
File string `yaml:"file"`
|
||||
}
|
||||
|
||||
type PerformanceConfig struct {
|
||||
MaxConcurrentOperations int `yaml:"max_concurrent_operations"`
|
||||
ReadTimeout time.Duration `yaml:"read_timeout"`
|
||||
WriteTimeout time.Duration `yaml:"write_timeout"`
|
||||
}
|
||||
|
||||
type RateLimitConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
RequestsPerSecond int `yaml:"requests_per_second"`
|
||||
Burst int `yaml:"burst"`
|
||||
}
|
||||
|
||||
type SecurityConfig struct {
|
||||
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
||||
MaxFileSize string `yaml:"max_file_size"`
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Server: ServerConfig{
|
||||
Name: "obsidian-mcp",
|
||||
Version: "1.0.0",
|
||||
Transport: "stdio",
|
||||
},
|
||||
Vault: VaultConfig{
|
||||
Path: "",
|
||||
ExcludePaths: []string{
|
||||
".obsidian/",
|
||||
".git/",
|
||||
".trash/",
|
||||
},
|
||||
WatchChanges: true,
|
||||
},
|
||||
Git: GitConfig{
|
||||
Enabled: true,
|
||||
AutoPull: true,
|
||||
AutoPush: false,
|
||||
AutoCommit: true,
|
||||
CommitPrefix: "[MCP]",
|
||||
Remote: "origin",
|
||||
Branch: "main",
|
||||
},
|
||||
Index: IndexConfig{
|
||||
BuildOnStartup: true,
|
||||
RebuildInterval: 5 * time.Minute,
|
||||
MaxNotesInMemory: 10000,
|
||||
},
|
||||
Search: SearchConfig{
|
||||
MaxResults: 50,
|
||||
ContextLines: 2,
|
||||
CaseSensitive: false,
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
Enabled: true,
|
||||
MaxSize: 1000,
|
||||
TTL: 5 * time.Minute,
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "text",
|
||||
File: "",
|
||||
},
|
||||
Performance: PerformanceConfig{
|
||||
MaxConcurrentOperations: 10,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
RateLimit: RateLimitConfig{
|
||||
Enabled: true,
|
||||
RequestsPerSecond: 10,
|
||||
Burst: 20,
|
||||
},
|
||||
MaxFileSize: "10MB",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
config := NewDefaultConfig()
|
||||
|
||||
if path == "" {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return config, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||
}
|
||||
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid config: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func LoadConfigFromEnv() (*Config, error) {
|
||||
config := NewDefaultConfig()
|
||||
|
||||
if vaultPath := os.Getenv("VAULT_PATH"); vaultPath != "" {
|
||||
config.Vault.Path = vaultPath
|
||||
}
|
||||
|
||||
if logLevel := os.Getenv("LOG_LEVEL"); logLevel != "" {
|
||||
config.Logging.Level = logLevel
|
||||
}
|
||||
|
||||
if gitEnabled := os.Getenv("GIT_ENABLED"); gitEnabled == "false" {
|
||||
config.Git.Enabled = false
|
||||
}
|
||||
|
||||
if gitAutoPush := os.Getenv("GIT_AUTO_PUSH"); gitAutoPush == "true" {
|
||||
config.Git.AutoPush = true
|
||||
}
|
||||
|
||||
if cacheEnabled := os.Getenv("CACHE_ENABLED"); cacheEnabled == "false" {
|
||||
config.Cache.Enabled = false
|
||||
}
|
||||
|
||||
return config, config.Validate()
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if c.Vault.Path == "" {
|
||||
return fmt.Errorf("vault path is required")
|
||||
}
|
||||
|
||||
if c.Server.Transport != "stdio" && c.Server.Transport != "sse" {
|
||||
return fmt.Errorf("invalid transport: %s", c.Server.Transport)
|
||||
}
|
||||
|
||||
if c.Search.MaxResults <= 0 {
|
||||
return fmt.Errorf("max results must be positive")
|
||||
}
|
||||
|
||||
if c.Cache.MaxSize <= 0 {
|
||||
return fmt.Errorf("cache max size must be positive")
|
||||
}
|
||||
|
||||
validLogLevels := map[string]bool{
|
||||
"debug": true,
|
||||
"info": true,
|
||||
"warn": true,
|
||||
"error": true,
|
||||
}
|
||||
|
||||
if !validLogLevels[c.Logging.Level] {
|
||||
return fmt.Errorf("invalid log level: %s", c.Logging.Level)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) SaveToFile(path string) error {
|
||||
data, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal config: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write config file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user