Refactor db and options to separate packages
This commit is contained in:
140
options/options.go
Normal file
140
options/options.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
logger "git.site.quack-lab.dev/dave/cylogger"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var GlobalOptions Options
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
GlobalOptions, err = LoadOptions()
|
||||
if err != nil {
|
||||
logger.Error("Failed to load options %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
DBPath string `env:"DB_PATH" default:"eve-pi.db" description:"Database file path"`
|
||||
Port string `env:"HTTP_SERVER_PORT" default:"3000" description:"HTTP server port"`
|
||||
|
||||
ClientID string `env:"ESI_CLIENT_ID" description:"EVE SSO client ID"`
|
||||
RedirectURI string `env:"ESI_REDIRECT_URI" default:"http://localhost:3000/callback" description:"EVE SSO redirect URI"`
|
||||
Scopes []string `env:"ESI_SCOPES" default:"esi-planets.manage_planets.v1" description:"EVE SSO scopes (space-separated)"`
|
||||
CacheValidity string `env:"ESI_CACHE_VALIDITY" default:"PT20M" description:"ESI cache validity in duration (e.g. PT20M for 20 minutes)"`
|
||||
|
||||
WebhookURL string `env:"WEBHOOK_URL" description:"Webhook URL for notifications"`
|
||||
WebhookEmail string `env:"WEBHOOK_EMAIL" description:"Webhook authentication email"`
|
||||
WebhookToken string `env:"WEBHOOK_TOKEN" description:"Webhook authentication token"`
|
||||
|
||||
LogLevel string `env:"LOG_LEVEL" default:"info" description:"Logging level (dump, trace, debug, info, warning, error)"`
|
||||
}
|
||||
|
||||
func LoadOptions() (Options, error) {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
return Options{}, fmt.Errorf("failed to load environment variables: %w", err)
|
||||
}
|
||||
|
||||
opts := Options{}
|
||||
optsType := reflect.TypeOf(opts)
|
||||
optsValue := reflect.ValueOf(&opts).Elem()
|
||||
|
||||
for i := 0; i < optsType.NumField(); i++ {
|
||||
field := optsType.Field(i)
|
||||
fieldValue := optsValue.Field(i)
|
||||
|
||||
envKey := field.Tag.Get("env")
|
||||
if envKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
envValue := os.Getenv(envKey)
|
||||
defaultValue := field.Tag.Get("default")
|
||||
|
||||
if envValue == "" {
|
||||
if defaultValue == "" {
|
||||
return Options{}, fmt.Errorf("required environment variable %s is not set", envKey)
|
||||
}
|
||||
envValue = defaultValue
|
||||
}
|
||||
|
||||
// Set the field value based on its type
|
||||
if err := setFieldValue(fieldValue, envValue, field.Type); err != nil {
|
||||
return Options{}, fmt.Errorf("invalid value for %s: %w", envKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func setFieldValue(field reflect.Value, value string, fieldType reflect.Type) error {
|
||||
switch fieldType.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(value)
|
||||
case reflect.Slice:
|
||||
if fieldType.Elem().Kind() == reflect.String {
|
||||
// Handle []string by splitting on spaces
|
||||
items := strings.Fields(value)
|
||||
slice := reflect.MakeSlice(fieldType, len(items), len(items))
|
||||
for i, item := range items {
|
||||
slice.Index(i).SetString(item)
|
||||
}
|
||||
field.Set(slice)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported field type: %s", fieldType.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateEnvExample creates a .env.example file from the Options struct
|
||||
func GenerateEnvExample() error {
|
||||
optsType := reflect.TypeOf(Options{})
|
||||
|
||||
// Generate the .env.example content dynamically
|
||||
content := "# EVE PI Configuration\n"
|
||||
|
||||
for i := 0; i < optsType.NumField(); i++ {
|
||||
field := optsType.Field(i)
|
||||
envKey := field.Tag.Get("env")
|
||||
if envKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
defaultValue := field.Tag.Get("default")
|
||||
description := field.Tag.Get("description")
|
||||
|
||||
// Generate example value
|
||||
exampleValue := generateExampleValue(field.Name, envKey, defaultValue)
|
||||
|
||||
content += fmt.Sprintf("# %s\n", description)
|
||||
content += fmt.Sprintf("%s=%s\n\n", envKey, exampleValue)
|
||||
}
|
||||
|
||||
file, err := os.Create(".env.example")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(content)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateExampleValue(fieldName, envKey, defaultValue string) string {
|
||||
// If there's a default value, use it
|
||||
if defaultValue != "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// If no default value, use placeholder
|
||||
return "your_" + strings.ToLower(envKey) + "_here"
|
||||
}
|
||||
Reference in New Issue
Block a user