diff --git a/.gitignore b/.gitignore index 93f48cc..3549d31 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,4 @@ go.work.sum export_chart/* -.env.local -.env.dev -.env.prd -.env.terra +.env.local* \ No newline at end of file diff --git a/backend/config/config.go b/backend/config/config.go index 0441d61..069e34f 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -94,6 +94,15 @@ func LoadConfig() *Config { // Database if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" { config.DatabaseURL = dbURL + } else { + host := os.Getenv("DB_HOST") + port := os.Getenv("DB_PORT") + user := os.Getenv("DB_USER") + password := os.Getenv("DB_PASSWORD") + dbName := os.Getenv("DB_NAME") + if composed := buildDatabaseURL(host, port, user, password, dbName); composed != "" { + config.DatabaseURL = composed + } } if dbType := os.Getenv("DATABASE_TYPE"); dbType != "" { config.DatabaseType = strings.ToLower(dbType) @@ -249,6 +258,19 @@ func loadEnvFileContent(filename string) { fmt.Printf("DEBUG loadEnvFileContent - Datei %s vollstΓ€ndig verarbeitet (%d Zeilen)\n", filename, lineNum) } +// buildDatabaseURL composes a MySQL/MariaDB DSN from individual connection parts. +// Returns an empty string if host, user, or dbName is missing. +func buildDatabaseURL(host, port, user, password, dbName string) string { + if host == "" || user == "" || dbName == "" { + return "" + } + if port == "" { + port = "3306" + } + return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + user, password, host, port, dbName) +} + // IsDevelopment prΓΌft, ob die Anwendung im Development-Modus lΓ€uft func (c *Config) IsDevelopment() bool { return c.Environment == "development" || c.Environment == "dev" diff --git a/backend/config/config_test.go b/backend/config/config_test.go index 33890d6..bd59227 100644 --- a/backend/config/config_test.go +++ b/backend/config/config_test.go @@ -205,3 +205,149 @@ DATABASE_URL=test://localhost/testdb` } } } + +func TestBuildDatabaseURL(t *testing.T) { + tests := []struct { + name string + host string + port string + user string + password string + dbName string + want string + }{ + { + name: "full mysql dsn", + host: "mariadb", + port: "3306", + user: "bamort", + password: "secret", + dbName: "bamort", + want: "bamort:secret@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local", + }, + { + name: "default port when empty", + host: "localhost", + port: "", + user: "user", + password: "pass", + dbName: "db", + want: "user:pass@tcp(localhost:3306)/db?charset=utf8mb4&parseTime=True&loc=Local", + }, + { + name: "empty host returns empty string", + host: "", + port: "3306", + user: "user", + password: "pass", + dbName: "db", + want: "", + }, + { + name: "empty user returns empty string", + host: "localhost", + port: "3306", + user: "", + password: "pass", + dbName: "db", + want: "", + }, + { + name: "empty dbName returns empty string", + host: "localhost", + port: "3306", + user: "user", + password: "pass", + dbName: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := buildDatabaseURL(tt.host, tt.port, tt.user, tt.password, tt.dbName) + if got != tt.want { + t.Errorf("buildDatabaseURL() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestLoadConfig_ComposeDatabaseURLFromParts(t *testing.T) { + setupTestEnvironment(t) + + vars := map[string]string{ + "DATABASE_URL": "", + "DATABASE_TYPE": "mysql", + "DB_HOST": "mariadb-dev", + "DB_PORT": "3306", + "DB_USER": "bamort", + "DB_PASSWORD": "secret", + "DB_NAME": "bamort", + } + originals := make(map[string]string, len(vars)) + for k := range vars { + originals[k] = os.Getenv(k) + os.Unsetenv(k) + } + t.Cleanup(func() { + for k, v := range originals { + if v != "" { + os.Setenv(k, v) + } else { + os.Unsetenv(k) + } + } + }) + + for k, v := range vars { + if v != "" { + os.Setenv(k, v) + } + } + + cfg := LoadConfig() + + want := "bamort:secret@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local" + if cfg.DatabaseURL != want { + t.Errorf("DatabaseURL = %q, want %q", cfg.DatabaseURL, want) + } +} + +func TestLoadConfig_ExplicitDatabaseURLTakesPrecedence(t *testing.T) { + setupTestEnvironment(t) + + explicit := "bamort:override@tcp(other-host:3307)/otherdb?charset=utf8mb4&parseTime=True&loc=Local" + vars := map[string]string{ + "DATABASE_URL": explicit, + "DB_HOST": "mariadb-dev", + "DB_PORT": "3306", + "DB_USER": "bamort", + "DB_PASSWORD": "secret", + "DB_NAME": "bamort", + } + originals := make(map[string]string, len(vars)) + for k := range vars { + originals[k] = os.Getenv(k) + os.Unsetenv(k) + } + t.Cleanup(func() { + for k, v := range originals { + if v != "" { + os.Setenv(k, v) + } else { + os.Unsetenv(k) + } + } + }) + + for k, v := range vars { + os.Setenv(k, v) + } + + cfg := LoadConfig() + + if cfg.DatabaseURL != explicit { + t.Errorf("DatabaseURL = %q, want %q", cfg.DatabaseURL, explicit) + } +} diff --git a/docker/.env.example b/docker/.env.example index ee0a5f4..16b8122 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -15,7 +15,11 @@ API_PORT=8180 #- Database Configuration Backend DATABASE_TYPE=mysql -#DATABASE_URL=bamort:your_secure_user_password@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local +DB_HOST=mariadb +DB_PORT=3306 +DB_USER=bamort +DB_NAME=bamort +DB_ROOT_PASSWORD=root_password_dev #- MariaDB Configuration MARIADB_ROOT_PASSWORD=your_secure_root_password diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 7e387a2..3be37fd 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -12,7 +12,11 @@ services: - GO_ENV=development - CGO_ENABLED=1 - DATABASE_TYPE=${DATABASE_TYPE:-mysql} - - DATABASE_URL=${DATABASE_URL:-${MARIADB_USER:-bamort}:${MARIADB_PASSWORD:-secure_user_password}@tcp(mariadb-dev:3306)/${MARIADB_DATABASE:-bamort}?charset=utf8mb4&parseTime=True&loc=Local} + - DB_HOST=${DB_HOST:-mariadb-dev} + - DB_PORT=${DB_PORT:-3306} + - DB_USER=${DB_USER:-bamort} + - DB_PASSWORD=${DB_PASSWORD:-secure_user_password} + - DB_NAME=${DB_NAME:-bamort} - API_PORT=${API_PORT:-8180} - TEMPLATES_DIR=${TEMPLATES_DIR:-./templatesx} - EXPORT_TEMP_DIR=${EXPORT_TEMP_DIR:-./export_tempx} @@ -53,10 +57,10 @@ services: ports: - "3306:3306" environment: - MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} - MARIADB_DATABASE: ${MARIADB_DATABASE:-bamort} - MARIADB_USER: ${MARIADB_USER:-bamort} - MARIADB_PASSWORD: ${MARIADB_PASSWORD:-secure_user_password} + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} + MARIADB_DATABASE: ${DB_NAME:-bamort} + MARIADB_USER: ${DB_USER:-bamort} + MARIADB_PASSWORD: ${DB_PASSWORD:-secure_user_password} MARIADB_CHARSET: utf8mb4 MARIADB_COLLATION: utf8mb4_unicode_ci volumes: @@ -75,11 +79,11 @@ services: ports: - "8081:80" environment: - PMA_HOST: mariadb-dev - PMA_PORT: 3306 + PMA_HOST: ${DB_HOST:-mariadb-dev} + PMA_PORT: ${DB_PORT:-3306} PMA_USER: root - PMA_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} - MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} + PMA_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} PMA_ARBITRARY: 1 depends_on: mariadb-dev: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index aa96275..ca1fb6e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,9 +11,15 @@ services: - GO_ENV=production - CGO_ENABLED=1 - DATABASE_TYPE=${DATABASE_TYPE:-mysql} - - DATABASE_URL=${MARIADB_USER:-bamort}:${MARIADB_PASSWORD:-secure_user_password}@tcp(mariadb:3306)/${MARIADB_DATABASE:-bamort}?charset=utf8mb4&parseTime=True&loc=Local - - BASE_URL=${BASE_URL:-https://bamort.trokan.de} + - DB_HOST=${DB_HOST:-mariadb} + - DB_PORT=${DB_PORT:-3306} + - DB_USER=${DB_USER:-bamort} + - DB_PASSWORD=${DB_PASSWORD:-secure_user_password} + - DB_NAME=${DB_NAME:-bamort} - API_PORT=${API_PORT:-8180} + - TEMPLATES_DIR=${TEMPLATES_DIR:-./templatesx} + - EXPORT_TEMP_DIR=${EXPORT_TEMP_DIR:-./export_tempx} + - BASE_URL=${BASE_URL:-https://bamort.trokan.de} depends_on: mariadb: condition: service_healthy @@ -44,10 +50,10 @@ services: #ports: # - "3306:3306" environment: - MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} - MARIADB_DATABASE: ${MARIADB_DATABASE:-bamort} - MARIADB_USER: ${MARIADB_USER:-bamort} - MARIADB_PASSWORD: ${MARIADB_PASSWORD:-secure_user_password} + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} + MARIADB_DATABASE: ${DB_NAME:-bamort} + MARIADB_USER: ${DB_USER:-bamort} + MARIADB_PASSWORD: ${DB_PASSWORD:-secure_user_password} MARIADB_CHARSET: utf8mb4 MARIADB_COLLATION: utf8mb4_unicode_ci volumes: @@ -68,11 +74,11 @@ services: # ports: # - "8081:80" # environment: - # PMA_HOST: mariadb - # PMA_PORT: 3306 + # PMA_HOST: ${DB_HOST:-mariadb} + # PMA_PORT: ${DB_PORT:-3306} # PMA_USER: root - # PMA_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} - # MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-secure_root_password} + # PMA_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} + # MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secure_root_password} # PMA_ARBITRARY: 1 # depends_on: # mariadb: diff --git a/docker/start-dev.sh b/docker/start-dev.sh index 51d8650..8280558 100755 --- a/docker/start-dev.sh +++ b/docker/start-dev.sh @@ -12,15 +12,14 @@ fi cd "$(dirname "$0")" # Load development environment variables -if [ -f .env.dev ]; then - echo "πŸ“ Loading configuration from .env.dev" - export $(grep -v '^#' .env.dev | xargs) -else - if [ -f .env ]; then - echo "πŸ“ Loading configuration from .env" - export $(grep -v '^#' .env | xargs) +if [ -f .env ]; then + echo "πŸ“ Loading configuration from .env" + export $(grep -v '^#' .env | xargs) + if [ -f .env.local ]; then + echo "πŸ“ Loading configuration from .env.local" + export $(grep -v '^#' .env.local | xargs) else - echo "⚠️ Warning: .env not found, using defaults" + echo "⚠️ Warning: .env.local not found, using defaults" fi fi @@ -35,7 +34,12 @@ echo "πŸ”§ Frontend will use API: ${API_URL:-http://localhost:8180}" docker-compose -f docker-compose.dev.yml -p bamort down # Baue und starte die Container -docker-compose -f docker-compose.dev.yml --env-file .env.dev -p bamort up --build -d +ENV_FILES="--env-file .env" +if [ -f .env.local ]; then + ENV_FILES="$ENV_FILES --env-file .env.local" + echo "πŸ“ Applying overrides from .env.local" +fi +docker-compose -f docker-compose.dev.yml $ENV_FILES -p bamort up --build -d echo "βœ… Development environment started." echo "πŸ“± Frontend: http://localhost:5173" diff --git a/docker/start-prd.sh b/docker/start-prd.sh index 02c9b32..be9c09e 100755 --- a/docker/start-prd.sh +++ b/docker/start-prd.sh @@ -12,15 +12,14 @@ fi cd "$(dirname "$0")" # Load production environment variables -if [ -f .env.prd ]; then - echo "πŸ“ Loading configuration from .env.prd" - export $(grep -v '^#' .env.prd | xargs) -else - if [ -f .env ]; then - echo "πŸ“ Loading configuration from .env" - export $(grep -v '^#' .env | xargs) +if [ -f .env ]; then + echo "πŸ“ Loading configuration from .env" + export $(grep -v '^#' .env | xargs) + if [ -f .env.local ]; then + echo "πŸ“ Loading configuration from .env.local" + export $(grep -v '^#' .env.local | xargs) else - echo "⚠️ Warning: .env not found, using defaults" + echo "⚠️ Warning: .env.local not found, using defaults" fi fi @@ -35,10 +34,16 @@ echo "πŸ”§ Frontend will use API: ${API_URL:-https://bamort-api.trokan.de}" cp ../docker/frontend-entrypoint.sh ../frontend/docker-entrypoint.sh # Build only if images for this commit don't exist yet +ENV_FILES="--env-file .env" +if [ -f .env.local ]; then + ENV_FILES="$ENV_FILES --env-file .env.local" + echo "πŸ“ Applying overrides from .env.local" +fi + if ! docker image inspect "bamort-backend:${GIT_TAG}" > /dev/null 2>&1 || \ ! docker image inspect "bamort-frontend:${GIT_TAG}" > /dev/null 2>&1; then echo "πŸ”¨ Images not found for tag '${GIT_TAG}', building..." - docker-compose -f docker-compose.yml --env-file .env.prd -p bamort build + docker-compose -f docker-compose.yml $ENV_FILES -p bamort build else echo "βœ… Images already exist for tag '${GIT_TAG}', skipping build." fi @@ -47,7 +52,7 @@ fi docker-compose -f docker-compose.yml -p bamort down # Baue und starte die Container -docker-compose -f docker-compose.yml --env-file .env.prd -p bamort up -d +docker-compose -f docker-compose.yml $ENV_FILES -p bamort up -d echo "βœ… Production environment started." echo "πŸ“± Frontend: http://localhost:8181"