Files
CalendarApi/internal/config/config.go
Michilis bd24545b7b Fix BASE_URL config loading, add tasks/projects; robust .env path resolution
- Config: try ENV_FILE, .env, ../.env for loading; trim trailing slash from BaseURL
- Log BASE_URL at server startup for verification
- .env.example: document BASE_URL
- Tasks, projects, tags, migrations and related API/handlers

Made-with: Cursor
2026-03-09 18:57:51 +00:00

135 lines
2.8 KiB
Go

package config
import (
"bufio"
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
type Config struct {
DatabaseURL string
JWTSecret string
RedisAddr string
ServerPort string
Env string
BaseURL string
CORSOrigins []string
RateLimitRPS float64
RateLimitBurst int
}
func Load() *Config {
loadEnvFromPath()
port := getEnv("SERVER_PORT", "8080")
jwtSecret := getEnv("JWT_SECRET", "dev-secret-change-me")
env := getEnv("ENV", "development")
if env == "production" && (jwtSecret == "" || jwtSecret == "dev-secret-change-me") {
log.Fatal("JWT_SECRET must be set to a secure value in production")
}
corsOrigins := parseCORSOrigins(getEnv("CORS_ORIGINS", "http://localhost:5173,http://127.0.0.1:5173"))
rateRPS := parseFloat(getEnv("RATE_LIMIT_RPS", "100"), 100)
rateBurst := parseInt(getEnv("RATE_LIMIT_BURST", "200"), 200)
return &Config{
DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/calendarapi?sslmode=disable"),
JWTSecret: jwtSecret,
RedisAddr: os.Getenv("REDIS_ADDR"),
ServerPort: port,
Env: env,
BaseURL: strings.TrimSuffix(getEnv("BASE_URL", "http://localhost:"+port), "/"),
CORSOrigins: corsOrigins,
RateLimitRPS: rateRPS,
RateLimitBurst: rateBurst,
}
}
func parseCORSOrigins(s string) []string {
if s == "" {
return []string{}
}
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
if len(out) == 0 {
return []string{"http://localhost:5173", "http://127.0.0.1:5173"}
}
return out
}
func parseFloat(s string, def float64) float64 {
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return def
}
return v
}
func parseInt(s string, def int) int {
v, err := strconv.Atoi(s)
if err != nil {
return def
}
return v
}
func loadEnvFromPath() {
if p := os.Getenv("ENV_FILE"); p != "" {
loadEnvFile(p)
return
}
cwd, _ := os.Getwd()
for _, p := range []string{".env", "../.env"} {
full := filepath.Join(cwd, p)
if f, err := os.Open(full); err == nil {
f.Close()
loadEnvFile(full)
return
}
}
// Fallback: try current dir as-is (for relative paths)
loadEnvFile(".env")
}
func loadEnvFile(path string) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
k, v, ok := strings.Cut(line, "=")
if !ok {
continue
}
k = strings.TrimSpace(k)
v = strings.TrimSpace(v)
if os.Getenv(k) == "" {
os.Setenv(k, v)
}
}
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}