146 lines
5.5 KiB
Go
146 lines
5.5 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
type Config struct {
|
|
Env string
|
|
Port string
|
|
DBName string
|
|
DBUser string
|
|
DBPassword string
|
|
DBPort string
|
|
DBHost string
|
|
RedisHost string
|
|
RedisPort string
|
|
RedisUser string
|
|
RedisPassword string
|
|
JWTSecret string
|
|
NatsURL string
|
|
NatsUser string
|
|
NatsPassword string
|
|
AILayerBaseURL string // AI decision-engine service base URL (e.g. http://rider-api:8082)
|
|
|
|
// RouteOptimizerURL is the Route Optimization API that orders a rider's
|
|
// stops (Valhalla-backed road sequencing). Empty disables sequencing: stops
|
|
// stay unordered rather than assignment failing.
|
|
RouteOptimizerURL string
|
|
|
|
// GeocoderURL is the Nominatim-compatible geocoding service the customer
|
|
// app'''s place search and reverse geocode are proxied through. Proxied on
|
|
// purpose: the legacy rider app shipped a Google Maps key inside the
|
|
// binary and it had to be revoked, so the customer app is never handed a
|
|
// key at all — it asks this service and this service asks the geocoder.
|
|
GeocoderURL string
|
|
// GeocoderEmail is the contact address Nominatim'''s usage policy asks
|
|
// callers to identify themselves with. Sent as the User-Agent contact;
|
|
// requests without one are throttled or blocked.
|
|
GeocoderEmail string
|
|
|
|
// TrustedProxies is a comma-separated list of reverse-proxy IPs/CIDRs that
|
|
// are allowed to set X-Forwarded-For. Rate limiting keys on the client IP,
|
|
// so behind a proxy this MUST be set — otherwise every request appears to
|
|
// come from the proxy and the whole fleet shares one limit bucket.
|
|
// Empty means "no proxy": the socket peer address is used as-is.
|
|
TrustedProxies string
|
|
SMTPHost string
|
|
SMTPPort string
|
|
SMTPUser string
|
|
SMTPPassword string
|
|
SMTPFrom string
|
|
|
|
// ClientOnboardingOwners are the console logins allowed to onboard a new
|
|
// client (tenant + its console login). Comma-separated emails, compared
|
|
// case-insensitively. Deliberately a short allow-list rather than a role:
|
|
// every Doormile admin has roleid 1, and onboarding mints credentials.
|
|
ClientOnboardingOwners []string
|
|
|
|
// The Agent Studio Test playground's model: any OpenAI-compatible chat
|
|
// completions API (Groq by default; xAI works too). Empty API key leaves
|
|
// the playground off — the endpoint answers 503 PLAYGROUND_NOT_CONFIGURED.
|
|
// Set the key as a secret in the deployment, never in a tracked file.
|
|
PlaygroundLLMBaseURL string
|
|
PlaygroundLLMAPIKey string
|
|
PlaygroundLLMModel string
|
|
}
|
|
|
|
func Load() *Config {
|
|
return &Config{
|
|
Env: getEnv("ENV", "development"),
|
|
Port: getEnv("APP_PORT", "8081"),
|
|
DBName: getEnv("DB_NAME", "logistics"),
|
|
DBUser: getEnv("DB_USER", "admin"),
|
|
DBPassword: getEnv("DB_PASSWORD", "Package@321#"),
|
|
DBPort: getEnv("DB_PORT", "5433"),
|
|
DBHost: getEnv("DB_HOST", "127.0.0.1"),
|
|
RedisHost: getEnv("REDIS_HOST", "127.0.0.1"),
|
|
RedisPort: getEnv("REDIS_PORT", "6379"),
|
|
RedisUser: getEnv("REDIS_USER", ""),
|
|
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
|
JWTSecret: getEnv("JWT_SECRET_KEY", "DoormileSuperSecretJWTKey2026!"),
|
|
NatsURL: getEnv("NATS_URL", "nats://66.116.226.161:4223"),
|
|
NatsUser: getEnv("NATS_USER", "doormile"),
|
|
NatsPassword: getEnv("NATS_PASSWORD", "Package@321#"),
|
|
AILayerBaseURL: getEnv("AI_LAYER_BASE_URL", "https://routemate.workolik.com"),
|
|
|
|
RouteOptimizerURL: getEnv("ROUTE_OPTIMIZER_URL", "https://routes.workolik.com"),
|
|
GeocoderURL: getEnv("GEOCODER_URL", "https://nominatim.openstreetmap.org"),
|
|
GeocoderEmail: getEnv("GEOCODER_EMAIL", ""),
|
|
TrustedProxies: getEnv("TRUSTED_PROXIES", ""),
|
|
SMTPHost: getEnv("SMTP_HOST", ""),
|
|
SMTPPort: getEnv("SMTP_PORT", "465"),
|
|
SMTPUser: getEnv("SMTP_USER", ""),
|
|
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
|
|
SMTPFrom: getEnv("SMTP_FROM", ""),
|
|
|
|
ClientOnboardingOwners: splitEmails(getEnv("CLIENT_ONBOARDING_OWNERS", "admin@doormile.com")),
|
|
|
|
PlaygroundLLMBaseURL: getEnv("PLAYGROUND_LLM_BASE_URL", "https://api.groq.com/openai/v1"),
|
|
PlaygroundLLMAPIKey: getEnv("PLAYGROUND_LLM_API_KEY", ""),
|
|
PlaygroundLLMModel: getEnv("PLAYGROUND_LLM_MODEL", "openai/gpt-oss-120b"),
|
|
}
|
|
}
|
|
|
|
// requiredInProduction are the secrets whose development fallback above is a
|
|
// literal committed to this repository. In production a missing one must stop
|
|
// the boot: falling back would sign every token with a JWT secret anyone with
|
|
// the source can read, and connect with a published password.
|
|
var requiredInProduction = []string{"JWT_SECRET_KEY", "DB_PASSWORD", "NATS_PASSWORD"}
|
|
|
|
// MissingProductionSecrets names each required secret that is unset when
|
|
// ENV=production. Always empty in any other environment, so local development
|
|
// keeps running on the fallbacks.
|
|
func (c *Config) MissingProductionSecrets() []string {
|
|
if !strings.EqualFold(c.Env, "production") {
|
|
return nil
|
|
}
|
|
var missing []string
|
|
for _, key := range requiredInProduction {
|
|
if os.Getenv(key) == "" {
|
|
missing = append(missing, key)
|
|
}
|
|
}
|
|
return missing
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// splitEmails parses a comma-separated email list: trimmed, lower-cased,
|
|
// blanks dropped.
|
|
func splitEmails(v string) []string {
|
|
var out []string
|
|
for _, e := range strings.Split(v, ",") {
|
|
if e = strings.ToLower(strings.TrimSpace(e)); e != "" {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|