diff --git a/internal/config/admin.go b/internal/config/admin.go new file mode 100644 index 0000000..a41bc88 --- /dev/null +++ b/internal/config/admin.go @@ -0,0 +1,7 @@ +package config + +type AdminConfig struct { + Name string `env:"GUARD_ADMIN_NAME" default:"Admin"` + Email string `env:"GUARD_ADMIN_EMAIL" required:"true"` + Password string `env:"GUARD_ADMIN_PASSWORD" required:"true"` +} diff --git a/internal/config/jwt.go b/internal/config/jwt.go new file mode 100644 index 0000000..be5edbc --- /dev/null +++ b/internal/config/jwt.go @@ -0,0 +1,8 @@ +package config + +type JwtConfig struct { + PrivateKey string `env:"GUARD_JWT_PRIVATE" required:"true"` + PublicKey string `env:"GUARD_JWT_PUBLIC" required:"true"` + KID string `env:"GUARD_JWT_KID" default:"guard-rsa"` + Issuer string `env:"GUARD_JWT_ISSUER" required:"true"` +} diff --git a/internal/config/minio.go b/internal/config/minio.go new file mode 100644 index 0000000..090f0a4 --- /dev/null +++ b/internal/config/minio.go @@ -0,0 +1,7 @@ +package config + +type MinioConfig struct { + Endpoint string `env:"GUARD_MINIO_ENDPOINT" default:"localhost:9000"` + AccessKey string `env:"GUARD_MINIO_ACCESS_KEY" required:"true"` + SecretKey string `env:"GUARD_MINIO_SECRET_KEY" required:"true"` +} diff --git a/internal/config/mod.go b/internal/config/mod.go new file mode 100644 index 0000000..95a0308 --- /dev/null +++ b/internal/config/mod.go @@ -0,0 +1,98 @@ +package config + +import ( + "errors" + "fmt" + "os" + "reflect" + "strconv" + "strings" +) + +type AppConfig struct { + Port string `env:"GUARD_PORT" default:"3001"` + Host string `env:"GUARD_HOST" default:"127.0.0.1"` + DatabaseURL string `env:"GUARD_DB_URL" required:"true"` + Admin AdminConfig + Jwt JwtConfig + Minio MinioConfig +} + +func LoadEnv(target any) error { + v := reflect.ValueOf(target) + if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct { + return &InvalidTargetError{} + } + return loadStruct(v.Elem(), "") +} + +type InvalidTargetError struct{} + +func (e *InvalidTargetError) Error() string { + return "target must be a pointer to a struct" +} + +var ErrMissingRequiredEnv = errors.New("missing required environment variable") + +func loadStruct(v reflect.Value, prefix string) error { + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + valueField := v.Field(i) + + if !valueField.CanSet() { + continue + } + + envKey := field.Tag.Get("env") + if envKey == "" { + envKey = strings.ToUpper(prefix + "_" + field.Name) + envKey = strings.TrimPrefix(envKey, "_") + } + + required := field.Tag.Get("required") == "true" + defaultVal := field.Tag.Get("default") + + if field.Type.Kind() == reflect.Struct { + err := loadStruct(valueField, envKey) + if err != nil { + return err + } + continue + } + + envVal := os.Getenv(envKey) + + if envVal == "" { + if defaultVal != "" { + envVal = defaultVal + } else if required { + return fmt.Errorf("%w: %s", ErrMissingRequiredEnv, envKey) + } else { + continue + } + } + + switch field.Type.Kind() { + case reflect.String: + valueField.SetString(envVal) + case reflect.Int, reflect.Int64: + i, err := strconv.ParseInt(envVal, 10, 64) + if err != nil { + return fmt.Errorf("invalid int for %s: %w", envKey, err) + } + valueField.SetInt(i) + case reflect.Bool: + b, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("invalid bool for %s: %w", envKey, err) + } + valueField.SetBool(b) + default: + return fmt.Errorf("unsupported type for field %s", field.Name) + } + } + + return nil +}