package config import ( "os" "strconv" "strings" ) type Config struct { Addr string DatabaseURL string // RunWalletAuthSchema: when true, API startup runs CREATE TABLE for wallet_auth_* (use a DDL-capable DB user). // Set false on read-only / public API hosts after schema has been applied elsewhere (e.g. admin/migrate host). RunWalletAuthSchema bool JWTSecret string UploadDir string UploadMaxMem int64 // passed to ParseMultipartForm (file parts buffer before spilling to disk) SeedAdmin bool AdminEmail string AdminPass string CORSOrigins []string // S3 (optional). When S3Bucket is set, admin uploads go to S3; use AWS SDK default credentials (env or IAM). S3Bucket string AWSRegion string S3UploadPrefix string S3PublicBase string // optional CDN / website endpoint base URL for returned object URLs // S3ObjectACL: optional canned ACL on PutObject (e.g. public-read). Empty = omit ACL (required for Bucket owner enforced buckets). S3ObjectACL string } func Load() Config { origins := os.Getenv("CORS_ORIGINS") var cors []string if origins != "" { for _, o := range strings.Split(origins, ",") { o = strings.TrimSpace(o) if o != "" { cors = append(cors, o) } } } addr := os.Getenv("HTTP_ADDR") if addr == "" { addr = ":8080" } db := os.Getenv("DATABASE_URL") if db == "" { db = "postgres://ark:ark@localhost:5432/arkdb?sslmode=disable" } secret := os.Getenv("JWT_SECRET") if secret == "" { secret = "dev-insecure-change-me" } upload := os.Getenv("UPLOAD_DIR") if upload == "" { upload = "./uploads" } // Memory buffer for multipart file parts (bytes beyond this spill to temp files). uploadMem := int64(64 << 20) if s := strings.TrimSpace(os.Getenv("UPLOAD_MULTIPART_MEM_MB")); s != "" { if mb, err := strconv.ParseInt(s, 10, 64); err == nil && mb > 0 { uploadMem = mb * 1 << 20 } } s3Bucket := strings.TrimSpace(os.Getenv("S3_BUCKET")) awsRegion := strings.TrimSpace(os.Getenv("AWS_REGION")) if awsRegion == "" { awsRegion = strings.TrimSpace(os.Getenv("AWS_DEFAULT_REGION")) } if s3Bucket != "" && awsRegion == "" { awsRegion = "ap-southeast-1" } s3Prefix := strings.TrimSpace(os.Getenv("S3_UPLOAD_PREFIX")) if s3Prefix == "" { s3Prefix = "uploads" } s3Prefix = strings.Trim(s3Prefix, "/") s3Public := strings.TrimSpace(os.Getenv("S3_PUBLIC_BASE_URL")) s3ObjectACL := strings.TrimSpace(os.Getenv("S3_OBJECT_ACL")) return Config{ Addr: addr, DatabaseURL: db, RunWalletAuthSchema: envBoolDefaultTrue("RUN_WALLET_AUTH_SCHEMA"), JWTSecret: secret, UploadDir: upload, UploadMaxMem: uploadMem, SeedAdmin: os.Getenv("SEED_ADMIN") == "1" || os.Getenv("SEED_ADMIN") == "true", AdminEmail: firstNonEmpty(os.Getenv("ADMIN_EMAIL"), "admin@ark.local"), AdminPass: firstNonEmpty(os.Getenv("ADMIN_PASSWORD"), "admin123"), CORSOrigins: cors, S3Bucket: s3Bucket, AWSRegion: awsRegion, S3UploadPrefix: s3Prefix, S3PublicBase: s3Public, S3ObjectACL: s3ObjectACL, } } // envBoolDefaultTrue returns true when the env var is unset; false for 0/false/no/off (case-insensitive). func envBoolDefaultTrue(key string) bool { s := strings.ToLower(strings.TrimSpace(os.Getenv(key))) if s == "" { return true } if s == "0" || s == "false" || s == "no" || s == "off" { return false } return s == "1" || s == "true" || s == "yes" || s == "on" } func firstNonEmpty(a, b string) string { if strings.TrimSpace(a) != "" { return a } return b }