Files
spsays-bff/internal/config/config.go
T
2026-05-30 02:33:57 +05:30

150 lines
3.9 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"time"
"github.com/joho/godotenv"
)
// Config holds all configuration for the application.
type Config struct {
// Server
Env string // "development" | "production"
Port int
// MongoDB (content database)
MongoURI string
MongoDB string
// PostgreSQL (users, relations, URL shortener)
PostgresURI string
// Typesense
TypesenseURL string
TypesenseAPIKey string
// DragonflyDB / Redis
RedisURI string
// Cloudflare R2 (S3-compatible) — for avatar uploads via presigned URLs.
R2AccountID string
R2AccessKeyID string
R2SecretAccessKey string
R2Bucket string
R2PublicURL string // Public base URL for objects, e.g. https://cdn.example.com or the R2 public bucket URL
// SMTP — used for password-reset and email-verification mails.
SMTPHost string
SMTPPort int
SMTPUsername string
SMTPPassword string
SMTPFrom string // "Prabhupāda Says <noreply@prabhupadasays.org>"
// Push Notifications (existing — unchanged).
FCMCredentialsFile string
APNsKeyFile string
APNsKeyID string
APNsTeamID string
APNsBundleID string
// JWT Auth
JWTSecret string
JWTExpiration time.Duration // parsed at load time
CookieDomain string // empty in dev; ".prabhupadasays.org" in prod
CookieSecure bool // true in production
// Frontend
FrontendURL string // for CORS + reset/verify links in emails
}
// Load reads configuration from environment variables (with .env fallback).
func Load() (*Config, error) {
_ = godotenv.Load()
jwtExp, err := time.ParseDuration(getEnv("JWT_EXPIRATION", "168h"))
if err != nil {
return nil, fmt.Errorf("invalid JWT_EXPIRATION: %w", err)
}
cfg := &Config{
Env: getEnv("APP_ENV", "development"),
Port: getEnvInt("PORT", 8080),
MongoURI: getEnv("MONGO_URI", ""),
MongoDB: getEnv("MONGO_DB", "spsays"),
PostgresURI: getEnv("POSTGRES_URI", ""),
TypesenseURL: getEnv("TYPESENSE_URL", "https://search.prabhupadasays.org"),
TypesenseAPIKey: getEnv("TYPESENSE_API_KEY", ""),
RedisURI: getEnv("REDIS_URI", "redis://localhost:6379"),
R2AccountID: getEnv("R2_ACCOUNT_ID", ""),
R2AccessKeyID: getEnv("R2_ACCESS_KEY_ID", ""),
R2SecretAccessKey: getEnv("R2_SECRET_ACCESS_KEY", ""),
R2Bucket: getEnv("R2_BUCKET", ""),
R2PublicURL: getEnv("R2_PUBLIC_URL", ""),
SMTPHost: getEnv("SMTP_HOST", ""),
SMTPPort: getEnvInt("SMTP_PORT", 587),
SMTPUsername: getEnv("SMTP_USERNAME", ""),
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
SMTPFrom: getEnv("SMTP_FROM", "noreply@localhost"),
FCMCredentialsFile: getEnv("FCM_CREDENTIALS_FILE", ""),
APNsKeyFile: getEnv("APNS_KEY_FILE", ""),
APNsKeyID: getEnv("APNS_KEY_ID", ""),
APNsTeamID: getEnv("APNS_TEAM_ID", ""),
APNsBundleID: getEnv("APNS_BUNDLE_ID", ""),
JWTSecret: getEnv("JWT_SECRET", ""),
JWTExpiration: jwtExp,
CookieDomain: getEnv("COOKIE_DOMAIN", ""),
CookieSecure: getEnvBool("COOKIE_SECURE", false),
FrontendURL: getEnv("FRONTEND_URL", "http://localhost:3000"),
}
if cfg.MongoURI == "" {
return nil, fmt.Errorf("MONGO_URI is required")
}
if cfg.Env == "production" && cfg.JWTSecret == "" {
return nil, fmt.Errorf("JWT_SECRET is required in production")
}
if cfg.JWTSecret == "" {
// Dev fallback so the server can boot without secrets configured.
cfg.JWTSecret = "dev-only-jwt-secret-do-not-use-in-production"
}
return cfg, nil
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func getEnvInt(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
if i, err := strconv.Atoi(v); err == nil {
return i
}
}
return fallback
}
func getEnvBool(key string, fallback bool) bool {
if v := os.Getenv(key); v != "" {
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return fallback
}