2018-10-19 15:31:41 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2019-01-11 15:49:21 -02:00
|
|
|
"fmt"
|
2019-01-11 15:13:42 -02:00
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
2025-07-01 23:07:44 +12:00
|
|
|
"strings"
|
2019-01-11 15:13:42 -02:00
|
|
|
|
2018-10-19 15:31:41 -04:00
|
|
|
"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"
|
2025-06-30 22:08:19 +12:00
|
|
|
"github.com/openaccounting/oa-server/core/repository"
|
2018-10-19 15:31:41 -04:00
|
|
|
"github.com/openaccounting/oa-server/core/util"
|
2025-06-30 22:08:19 +12:00
|
|
|
"github.com/openaccounting/oa-server/database"
|
|
|
|
|
"github.com/spf13/viper"
|
2018-10-19 15:31:41 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
2025-06-30 22:08:19 +12:00
|
|
|
// Initialize Viper configuration
|
2018-10-19 15:31:41 -04:00
|
|
|
var config types.Config
|
2025-06-30 22:08:19 +12:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
2025-07-01 23:07:44 +12:00
|
|
|
// 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")
|
|
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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")
|
|
|
|
|
|
2025-07-01 23:07:44 +12:00
|
|
|
// 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", "")
|
|
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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)
|
2018-10-19 15:31:41 -04:00
|
|
|
if err != nil {
|
2025-06-30 22:08:19 +12:00
|
|
|
log.Fatal(fmt.Errorf("failed to unmarshal config: %s", err.Error()))
|
2018-10-19 15:31:41 -04:00
|
|
|
}
|
2025-07-01 23:07:44 +12:00
|
|
|
|
|
|
|
|
// 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"
|
|
|
|
|
}
|
2018-10-19 15:31:41 -04:00
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-19 15:31:41 -04:00
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// Default to SQLite if no driver specified
|
|
|
|
|
driver := config.DatabaseDriver
|
|
|
|
|
if driver == "" {
|
|
|
|
|
driver = "sqlite"
|
2018-10-19 15:31:41 -04:00
|
|
|
}
|
|
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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
|
|
|
|
|
}
|
2018-10-19 15:31:41 -04:00
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
err = database.Connect(dbConfig)
|
2019-01-11 17:23:44 -02:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(fmt.Errorf("failed to connect to database with: %s", err.Error()))
|
|
|
|
|
}
|
2018-10-19 15:31:41 -04:00
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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()))
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-19 15:31:41 -04:00
|
|
|
bc := &util.StandardBcrypt{}
|
|
|
|
|
|
2025-06-30 22:08:19 +12:00
|
|
|
// 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
|
2018-10-19 15:31:41 -04:00
|
|
|
|
2025-07-01 23:07:44 +12:00
|
|
|
// 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()))
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-08 11:35:11 -05:00
|
|
|
app, err := api.Init(config.ApiPrefix)
|
2018-10-19 15:31:41 -04:00
|
|
|
if err != nil {
|
2019-01-11 16:15:49 -02:00
|
|
|
log.Fatal(fmt.Errorf("failed to create api instance with: %s", err.Error()))
|
2018-10-19 15:31:41 -04:00
|
|
|
}
|
|
|
|
|
|
2019-01-11 16:16:22 -02:00
|
|
|
if config.CertFile == "" || config.KeyFile == "" {
|
2019-01-11 16:16:56 -02:00
|
|
|
err = http.ListenAndServe(config.Address+":"+strconv.Itoa(config.Port), app.MakeHandler())
|
2019-01-11 16:16:22 -02:00
|
|
|
} else {
|
2019-01-11 16:16:56 -02:00
|
|
|
err = http.ListenAndServeTLS(config.Address+":"+strconv.Itoa(config.Port), config.CertFile, config.KeyFile, app.MakeHandler())
|
2019-01-11 16:16:22 -02:00
|
|
|
}
|
2019-01-11 16:15:49 -02:00
|
|
|
log.Fatal(fmt.Errorf("failed to start server with: %s", err.Error()))
|
2018-10-19 15:31:41 -04:00
|
|
|
}
|