191 lines
5.0 KiB
Go
191 lines
5.0 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
AppEnv string
|
|
Addr string
|
|
DatabaseURL string
|
|
RedisAddr string
|
|
RedisPassword string
|
|
RedisDB int
|
|
S3Endpoint string
|
|
S3PublicEndpoint string
|
|
S3Region string
|
|
S3Bucket string
|
|
S3AccessKey string
|
|
S3SecretKey string
|
|
S3UseSSL bool
|
|
PresignTTL time.Duration
|
|
DefaultExpiry time.Duration
|
|
AllowedOrigins []string
|
|
CreateLimit int
|
|
PublicReadLimit int
|
|
DownloadURLLimit int
|
|
CleanupInterval time.Duration
|
|
ChunkSize int64
|
|
MaxFileCount int
|
|
MaxTransferBytes int64
|
|
}
|
|
|
|
// AllowedExpiryOptions is the canonical list of transfer lifetimes accepted by the API.
|
|
var AllowedExpiryOptions = []time.Duration{
|
|
5 * time.Minute,
|
|
10 * time.Minute,
|
|
30 * time.Minute,
|
|
1 * time.Hour,
|
|
3 * time.Hour,
|
|
6 * time.Hour,
|
|
12 * time.Hour,
|
|
24 * time.Hour,
|
|
72 * time.Hour,
|
|
7 * 24 * time.Hour,
|
|
}
|
|
|
|
// Load reads environment variables, applies defaults, and validates required settings.
|
|
func Load() (Config, error) {
|
|
cfg := Config{
|
|
AppEnv: getenv("APP_ENV", "development"),
|
|
Addr: getenv("API_ADDR", ":8080"),
|
|
DatabaseURL: getenv("DATABASE_URL", "postgres://xdrop:xdrop@localhost:5432/xdrop?sslmode=disable"),
|
|
RedisAddr: getenv("REDIS_ADDR", "localhost:6379"),
|
|
RedisPassword: getenv("REDIS_PASSWORD", ""),
|
|
S3Endpoint: getenv("S3_ENDPOINT", "http://localhost:9000"),
|
|
S3PublicEndpoint: getenv("S3_PUBLIC_ENDPOINT", "http://localhost:5173"),
|
|
S3Region: getenv("S3_REGION", "us-east-1"),
|
|
S3Bucket: getenv("S3_BUCKET", "xdrop"),
|
|
S3AccessKey: getenv("S3_ACCESS_KEY", "minioadmin"),
|
|
S3SecretKey: getenv("S3_SECRET_KEY", "minioadmin"),
|
|
S3UseSSL: getenv("S3_USE_SSL", "false") == "true",
|
|
DefaultExpiry: getenvExpiry("DEFAULT_EXPIRY_SECONDS", "DEFAULT_EXPIRY_DAYS", time.Hour),
|
|
AllowedOrigins: splitCSV(getenv("ALLOWED_ORIGINS", "http://localhost:5173,http://localhost:8080")),
|
|
CreateLimit: getenvInt("RATE_LIMIT_CREATE", 20),
|
|
PublicReadLimit: getenvInt("RATE_LIMIT_PUBLIC_READ", 180),
|
|
DownloadURLLimit: getenvInt("RATE_LIMIT_DOWNLOAD_URLS", 120),
|
|
CleanupInterval: getenvDuration("CLEANUP_INTERVAL", 2*time.Minute),
|
|
ChunkSize: getenvInt64("CHUNK_SIZE_BYTES", 8*1024*1024),
|
|
MaxFileCount: getenvInt("MAX_FILE_COUNT", 100),
|
|
MaxTransferBytes: getenvInt64("MAX_TRANSFER_BYTES", 256*1024*1024),
|
|
}
|
|
|
|
cfg.RedisDB = getenvInt("REDIS_DB", 0)
|
|
presignSeconds := getenvInt("PRESIGN_TTL_SECONDS", 300)
|
|
cfg.PresignTTL = time.Duration(presignSeconds) * time.Second
|
|
|
|
if cfg.DatabaseURL == "" {
|
|
return Config{}, fmt.Errorf("DATABASE_URL is required")
|
|
}
|
|
if cfg.S3Bucket == "" {
|
|
return Config{}, fmt.Errorf("S3_BUCKET is required")
|
|
}
|
|
if !IsAllowedExpiry(cfg.DefaultExpiry) {
|
|
return Config{}, fmt.Errorf("DEFAULT_EXPIRY_SECONDS must be one of the supported expiry options")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func getenv(key, fallback string) string {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
func getenvInt(key string, fallback int) int {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
func getenvInt64(key string, fallback int64) int64 {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
|
|
parsed, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
func getenvDuration(key string, fallback time.Duration) time.Duration {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
|
|
parsed, err := time.ParseDuration(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
// getenvExpiry preserves support for the previous day-based variable while preferring seconds.
|
|
func getenvExpiry(secondsKey string, legacyDaysKey string, fallback time.Duration) time.Duration {
|
|
if seconds := strings.TrimSpace(os.Getenv(secondsKey)); seconds != "" {
|
|
parsed, err := strconv.Atoi(seconds)
|
|
if err == nil && parsed > 0 {
|
|
return time.Duration(parsed) * time.Second
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
if days := strings.TrimSpace(os.Getenv(legacyDaysKey)); days != "" {
|
|
parsed, err := strconv.Atoi(days)
|
|
if err == nil && parsed > 0 {
|
|
return time.Duration(parsed) * 24 * time.Hour
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
return fallback
|
|
}
|
|
|
|
// IsAllowedExpiry reports whether a duration matches one of the supported public options.
|
|
func IsAllowedExpiry(duration time.Duration) bool {
|
|
for _, option := range AllowedExpiryOptions {
|
|
if duration == option {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// splitCSV trims whitespace and drops empty values from comma-separated environment settings.
|
|
func splitCSV(value string) []string {
|
|
parts := strings.Split(value, ",")
|
|
origins := make([]string, 0, len(parts))
|
|
|
|
for _, part := range parts {
|
|
trimmed := strings.TrimSpace(part)
|
|
if trimmed != "" {
|
|
origins = append(origins, trimmed)
|
|
}
|
|
}
|
|
|
|
return origins
|
|
}
|