Fix up options to be more better

This commit is contained in:
2025-10-10 20:52:57 +02:00
parent 6d88711576
commit a4ecb92123
2 changed files with 118 additions and 45 deletions

17
main.go
View File

@@ -6,9 +6,10 @@ import (
"os" "os"
"os/signal" "os/signal"
logger "git.site.quack-lab.dev/dave/cylogger"
wh "go-eve-pi/webhook" wh "go-eve-pi/webhook"
logger "git.site.quack-lab.dev/dave/cylogger"
"github.com/fasthttp/router" "github.com/fasthttp/router"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@@ -16,7 +17,19 @@ import (
var webhook wh.Webhook var webhook wh.Webhook
func main() { func main() {
// Add flag for generating .env.example
help := flag.Bool("help", false, "Generate .env.example file")
flag.Parse() flag.Parse()
if *help {
if err := GenerateEnvExample(); err != nil {
logger.Error("Failed to generate .env.example: %v", err)
os.Exit(1)
}
logger.Info("Generated .env.example file successfully")
os.Exit(0)
}
logger.InitFlag() logger.InitFlag()
logger.Info("Starting Eve PI") logger.Info("Starting Eve PI")
@@ -31,7 +44,7 @@ func main() {
return return
} }
webhook = wh.NewZulipWebhook(options.ZulipURL, options.ZulipEmail, options.ZulipToken) webhook = wh.NewZulipWebhook(options.WebhookURL, options.WebhookEmail, options.WebhookToken)
// Setup fasthttp router // Setup fasthttp router
r := router.New() r := router.New()

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"reflect"
"strings" "strings"
logger "git.site.quack-lab.dev/dave/cylogger" logger "git.site.quack-lab.dev/dave/cylogger"
@@ -21,63 +22,122 @@ func init() {
} }
type Options struct { type Options struct {
DBPath string DBPath string `env:"DB_PATH" default:"eve-pi.db" description:"Database file path"`
Port string Port string `env:"HTTP_SERVER_PORT" default:"3000" description:"HTTP server port"`
ESIScopes []string ClientID string `env:"ESI_CLIENT_ID" required:"true" description:"EVE SSO client ID"`
ESIRedirectURI string RedirectURI string `env:"ESI_REDIRECT_URI" required:"true" description:"EVE SSO redirect URI"`
ESIClientID string Scopes []string `env:"ESI_SCOPES" default:"esi-planets.manage_planets.v1" description:"EVE SSO scopes (space-separated)"`
WebhookURL string WebhookURL string `env:"WEBHOOK_URL" required:"true" description:"Webhook URL for notifications"`
WebhookEmail string WebhookEmail string `env:"WEBHOOK_EMAIL" required:"true" description:"Webhook authentication email"`
WebhookToken string WebhookToken string `env:"WEBHOOK_TOKEN" required:"true" description:"Webhook authentication token"`
LogLevel string `env:"LOG_LEVEL" default:"info" description:"Logging level (debug, info, warning, error)"`
} }
func LoadOptions() (Options, error) { func LoadOptions() (Options, error) {
// Load environment variables strictly from .env file (fail if there's an error loading) err := godotenv.Load()
if err := godotenv.Load(); err != nil { if err != nil {
return Options{}, fmt.Errorf("error loading .env file: %w", err) return Options{}, fmt.Errorf("failed to load environment variables: %w", err)
} }
dbPath := getOrDefault("DB_PATH", "eve-pi.db") opts := Options{}
port := getOrDefault("HTTP_SERVER_PORT", "3000") optsType := reflect.TypeOf(opts)
optsValue := reflect.ValueOf(&opts).Elem()
clientID := getOrFatal("ESI_CLIENT_ID") for i := 0; i < optsType.NumField(); i++ {
redirectURI := getOrFatal("ESI_REDIRECT_URI") field := optsType.Field(i)
rawScopes := getOrDefault("ESI_SCOPES", "esi-planets.manage_planets.v1") fieldValue := optsValue.Field(i)
scopes := strings.Fields(rawScopes)
webhookUrl := getOrFatal("WEBHOOK_URL") envKey := field.Tag.Get("env")
webhookEmail := getOrFatal("WEBHOOK_EMAIL") if envKey == "" {
webhookToken := getOrFatal("WEBHOOK_TOKEN") continue
return Options{
DBPath: dbPath,
Port: port,
ESIClientID: clientID,
ESIRedirectURI: redirectURI,
ESIScopes: scopes,
WebhookURL: webhookUrl,
WebhookEmail: webhookEmail,
WebhookToken: webhookToken,
}, nil
}
func getOrFatal(key string) string {
value := os.Getenv(key)
if value == "" {
logger.Error("Environment variable %s is required", key)
os.Exit(1)
} }
return value
envValue := os.Getenv(envKey)
required := field.Tag.Get("required") == "true"
defaultValue := field.Tag.Get("default")
if envValue == "" {
if required {
return Options{}, fmt.Errorf("required environment variable %s is not set", envKey)
}
if defaultValue != "" {
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 getOrDefault(key, defaultValue string) string { func setFieldValue(field reflect.Value, value string, fieldType reflect.Type) error {
value := os.Getenv(key) switch fieldType.Kind() {
if value == "" { 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
}
required := field.Tag.Get("required") == "true"
defaultValue := field.Tag.Get("default")
description := field.Tag.Get("description")
// Generate example value
exampleValue := generateExampleValue(field.Name, envKey, defaultValue, required)
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, required bool) string {
// If there's a default value, use it
if defaultValue != "" {
return defaultValue return defaultValue
} }
return value
// If no default value, use placeholder
return "your_" + strings.ToLower(envKey) + "_here"
} }