mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-06-04 12:33:01 +00:00
initiall commit
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Env string
|
||||
ServerPort string
|
||||
DatabaseURL string
|
||||
DragonflyURL string
|
||||
StorageRoot string
|
||||
AuthInternalBaseURL string
|
||||
PublicURL string
|
||||
UserRateLimitPerMin int
|
||||
APIKeyRateLimitPerMin int
|
||||
|
||||
JWTIssuer string
|
||||
JWTAudience string
|
||||
JWTSecret string
|
||||
JWTTTLSeconds int
|
||||
|
||||
MailFrom string
|
||||
ResendAPIKey string
|
||||
SMTPHost string
|
||||
SMTPPort int
|
||||
SMTPUser string
|
||||
SMTPPassword string
|
||||
SMTPSecure bool
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
cfg := Config{
|
||||
Env: getenv("NODE_ENV", "development"),
|
||||
ServerPort: getenv("BACKEND_PORT", "8080"),
|
||||
DatabaseURL: os.Getenv("DATABASE_URL"),
|
||||
DragonflyURL: getenv("DRAGONFLY_URL", "redis://localhost:6379/0"),
|
||||
StorageRoot: getenv("BACKEND_STORAGE_ROOT", "./tmp/storage"),
|
||||
AuthInternalBaseURL: getenv("AUTH_INTERNAL_BASE_URL", "http://auth:3001"),
|
||||
PublicURL: getenv("VITE_APP_URL", "http://localhost"),
|
||||
UserRateLimitPerMin: 240,
|
||||
APIKeyRateLimitPerMin: 600,
|
||||
JWTIssuer: getenv("JWT_ISSUER", "primora-auth"),
|
||||
JWTAudience: getenv("JWT_AUDIENCE", "primora-api"),
|
||||
JWTSecret: os.Getenv("JWT_SECRET"),
|
||||
MailFrom: getenv("MAIL_FROM", "Primora <no-reply@primora.local>"),
|
||||
ResendAPIKey: os.Getenv("RESEND_API_KEY"),
|
||||
SMTPHost: getenv("SMTP_HOST", "localhost"),
|
||||
SMTPUser: os.Getenv("SMTP_USER"),
|
||||
SMTPPassword: os.Getenv("SMTP_PASSWORD"),
|
||||
}
|
||||
|
||||
smtpPort, err := strconv.Atoi(getenv("SMTP_PORT", "1025"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("parse SMTP_PORT: %w", err)
|
||||
}
|
||||
cfg.SMTPPort = smtpPort
|
||||
|
||||
jwtTTLSeconds, err := strconv.Atoi(getenv("JWT_TTL_SECONDS", "900"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("parse JWT_TTL_SECONDS: %w", err)
|
||||
}
|
||||
cfg.JWTTTLSeconds = jwtTTLSeconds
|
||||
|
||||
userRateLimitPerMin, err := parseNonNegativeIntEnv("USER_RATE_LIMIT_PER_MINUTE", cfg.UserRateLimitPerMin)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
cfg.UserRateLimitPerMin = userRateLimitPerMin
|
||||
|
||||
apiKeyRateLimitPerMin, err := parseNonNegativeIntEnv("API_KEY_RATE_LIMIT_PER_MINUTE", cfg.APIKeyRateLimitPerMin)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
cfg.APIKeyRateLimitPerMin = apiKeyRateLimitPerMin
|
||||
|
||||
smtpSecure, err := strconv.ParseBool(getenv("SMTP_SECURE", "false"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("parse SMTP_SECURE: %w", err)
|
||||
}
|
||||
cfg.SMTPSecure = smtpSecure
|
||||
|
||||
var missing []string
|
||||
if cfg.DatabaseURL == "" {
|
||||
missing = append(missing, "DATABASE_URL")
|
||||
}
|
||||
if cfg.JWTSecret == "" {
|
||||
missing = append(missing, "JWT_SECRET")
|
||||
}
|
||||
if cfg.StorageRoot == "" {
|
||||
missing = append(missing, "BACKEND_STORAGE_ROOT")
|
||||
}
|
||||
if cfg.AuthInternalBaseURL == "" {
|
||||
missing = append(missing, "AUTH_INTERNAL_BASE_URL")
|
||||
}
|
||||
if cfg.ResendAPIKey == "" && cfg.SMTPHost == "" {
|
||||
missing = append(missing, "RESEND_API_KEY or SMTP_HOST")
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return Config{}, errors.New("missing required environment values: " + strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func parseNonNegativeIntEnv(key string, fallback int) (int, error) {
|
||||
raw := os.Getenv(key)
|
||||
if raw == "" {
|
||||
return fallback, nil
|
||||
}
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse %s: %w", key, err)
|
||||
}
|
||||
if value < 0 {
|
||||
return 0, fmt.Errorf("%s must be >= 0", key)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setRequiredEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Setenv("DATABASE_URL", "postgres://primora:primora@localhost:5432/primora?sslmode=disable")
|
||||
t.Setenv("JWT_SECRET", "test-secret")
|
||||
t.Setenv("BACKEND_STORAGE_ROOT", "./tmp/storage")
|
||||
t.Setenv("AUTH_INTERNAL_BASE_URL", "http://auth:3001")
|
||||
t.Setenv("SMTP_HOST", "mailpit")
|
||||
}
|
||||
|
||||
func TestLoadRateLimitDefaults(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("USER_RATE_LIMIT_PER_MINUTE", "")
|
||||
t.Setenv("API_KEY_RATE_LIMIT_PER_MINUTE", "")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("load config: %v", err)
|
||||
}
|
||||
if cfg.UserRateLimitPerMin != 240 {
|
||||
t.Fatalf("unexpected USER_RATE_LIMIT_PER_MINUTE default: %d", cfg.UserRateLimitPerMin)
|
||||
}
|
||||
if cfg.APIKeyRateLimitPerMin != 600 {
|
||||
t.Fatalf("unexpected API_KEY_RATE_LIMIT_PER_MINUTE default: %d", cfg.APIKeyRateLimitPerMin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRateLimitRejectsInvalidNumber(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("USER_RATE_LIMIT_PER_MINUTE", "not-a-number")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Fatalf("expected parse error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "parse USER_RATE_LIMIT_PER_MINUTE") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRateLimitRejectsNegative(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("API_KEY_RATE_LIMIT_PER_MINUTE", "-1")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "API_KEY_RATE_LIMIT_PER_MINUTE must be >= 0") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user