package main import ( "fmt" "log" "net/http" "strconv" "strings" "github.com/openaccounting/oa-server/core/api" "github.com/openaccounting/oa-server/core/auth" "github.com/openaccounting/oa-server/core/model" "github.com/openaccounting/oa-server/core/model/types" "github.com/openaccounting/oa-server/core/repository" "github.com/openaccounting/oa-server/core/util" "github.com/openaccounting/oa-server/database" "github.com/spf13/viper" ) func main() { // Initialize Viper configuration var config types.Config // Set config file properties viper.SetConfigName("config") viper.SetConfigType("json") viper.AddConfigPath(".") viper.AddConfigPath("/etc/openaccounting/") viper.AddConfigPath("$HOME/.openaccounting") // Enable environment variables viper.AutomaticEnv() viper.SetEnvPrefix("OA") // will look for OA_DATABASE_PASSWORD, etc. // Configure Viper to handle nested config with environment variables viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Bind specific storage environment variables for better support // Using mapstructure field names (snake_case) viper.BindEnv("Storage.backend", "OA_STORAGE_BACKEND") viper.BindEnv("Storage.local.root_dir", "OA_STORAGE_LOCAL_ROOTDIR") viper.BindEnv("Storage.local.base_url", "OA_STORAGE_LOCAL_BASEURL") viper.BindEnv("Storage.s3.region", "OA_STORAGE_S3_REGION") viper.BindEnv("Storage.s3.bucket", "OA_STORAGE_S3_BUCKET") viper.BindEnv("Storage.s3.prefix", "OA_STORAGE_S3_PREFIX") viper.BindEnv("Storage.s3.access_key_id", "OA_STORAGE_S3_ACCESSKEYID") viper.BindEnv("Storage.s3.secret_access_key", "OA_STORAGE_S3_SECRETACCESSKEY") viper.BindEnv("Storage.s3.endpoint", "OA_STORAGE_S3_ENDPOINT") viper.BindEnv("Storage.s3.path_style", "OA_STORAGE_S3_PATHSTYLE") // Set default values viper.SetDefault("Address", "localhost") viper.SetDefault("Port", 8080) viper.SetDefault("DatabaseDriver", "sqlite") viper.SetDefault("DatabaseFile", "./openaccounting.db") viper.SetDefault("ApiPrefix", "/api/v1") // Set storage defaults (using mapstructure field names) viper.SetDefault("Storage.backend", "local") viper.SetDefault("Storage.local.root_dir", "./uploads") viper.SetDefault("Storage.local.base_url", "") // Read configuration err := viper.ReadInConfig() if err != nil { log.Printf("Warning: Could not read config file: %v", err) log.Println("Using environment variables and defaults") } // Unmarshal config into struct err = viper.Unmarshal(&config) if err != nil { log.Fatal(fmt.Errorf("failed to unmarshal config: %s", err.Error())) } // Set storage defaults if not configured (Viper doesn't handle nested defaults well) if config.Storage.Backend == "" { config.Storage.Backend = "local" } if config.Storage.Local.RootDir == "" { config.Storage.Local.RootDir = "./uploads" } // Parse database address (assuming format host:port for MySQL) host := config.DatabaseAddress port := "3306" if len(config.DatabaseAddress) > 0 { // If there's a colon, split host and port if colonIndex := len(config.DatabaseAddress); colonIndex > 0 { host = config.DatabaseAddress } } // Default to SQLite if no driver specified driver := config.DatabaseDriver if driver == "" { driver = "sqlite" } // Initialize GORM database dbConfig := &database.Config{ Driver: driver, Host: host, Port: port, User: config.User, Password: config.Password, DBName: config.Database, File: config.DatabaseFile, SSLMode: "disable", // Adjust as needed } err = database.Connect(dbConfig) if err != nil { log.Fatal(fmt.Errorf("failed to connect to database with: %s", err.Error())) } // Run migrations err = database.AutoMigrate() if err != nil { log.Fatal(fmt.Errorf("failed to run migrations: %s", err.Error())) } err = database.Migrate() if err != nil { log.Fatal(fmt.Errorf("failed to run custom migrations: %s", err.Error())) } bc := &util.StandardBcrypt{} // Create GORM repository and models gormRepo := repository.NewGormRepository(database.DB) gormModel := model.NewGormModel(database.DB, bc, config) auth.NewGormAuthService(gormRepo, bc) // Set the global model instance model.Instance = gormModel // Initialize storage backend for attachments err = api.InitializeAttachmentHandler(config.Storage) if err != nil { log.Fatal(fmt.Errorf("failed to initialize storage backend: %s", err.Error())) } app, err := api.Init(config.ApiPrefix) if err != nil { log.Fatal(fmt.Errorf("failed to create api instance with: %s", err.Error())) } if config.CertFile == "" || config.KeyFile == "" { err = http.ListenAndServe(config.Address+":"+strconv.Itoa(config.Port), app.MakeHandler()) } else { err = http.ListenAndServeTLS(config.Address+":"+strconv.Itoa(config.Port), config.CertFile, config.KeyFile, app.MakeHandler()) } log.Fatal(fmt.Errorf("failed to start server with: %s", err.Error())) }