From ef02bb810e270c4756b33bb8117d8463a3e58c73 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Fri, 10 Oct 2025 22:22:01 +0200 Subject: [PATCH] Refactor routes to separate file --- main.go | 192 ++++----------------------------------- routes/routes.go | 229 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 176 deletions(-) create mode 100644 routes/routes.go diff --git a/main.go b/main.go index c011f40..902bbd6 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,13 @@ package main import ( - "context" - "encoding/json" "flag" "os" "os/signal" - "strconv" "go-eve-pi/esi" + "go-eve-pi/options" + "go-eve-pi/routes" wh "go-eve-pi/webhook" logger "git.site.quack-lab.dev/dave/cylogger" @@ -26,7 +25,7 @@ func main() { if *help { flag.PrintDefaults() - if err := GenerateEnvExample(); err != nil { + if err := options.GenerateEnvExample(); err != nil { logger.Error("Failed to generate .env.example: %v", err) os.Exit(1) } @@ -34,21 +33,21 @@ func main() { os.Exit(0) } - logger.Init(logger.ParseLevel(options.LogLevel)) + logger.Init(logger.ParseLevel(options.GlobalOptions.LogLevel)) logger.Info("Starting Eve PI") // Create SSO instance - sso, err := NewSSO( - options.ClientID, - options.RedirectURI, - options.Scopes, + sso, err := esi.NewSSO( + options.GlobalOptions.ClientID, + options.GlobalOptions.RedirectURI, + options.GlobalOptions.Scopes, ) if err != nil { logger.Error("Failed to create SSO instance %v", err) return } - webhook = wh.NewZulipWebhook(options.WebhookURL, options.WebhookEmail, options.WebhookToken) + webhook = wh.NewZulipWebhook(options.GlobalOptions.WebhookURL, options.GlobalOptions.WebhookEmail, options.GlobalOptions.WebhookToken) // Setup fasthttp router r := router.New() @@ -56,175 +55,16 @@ func main() { // Configure SSO to use existing fasthttp router sso.SetRouter(r) - // Add your own routes - r.GET("/", func(ctx *fasthttp.RequestCtx) { - ctx.SetStatusCode(fasthttp.StatusOK) - ctx.WriteString("EVE PI Server Running") - }) + // Create ESI client + esiClient := esi.NewDirectESI() - r.GET("/login/{character}", func(ctx *fasthttp.RequestCtx) { - charName := ctx.UserValue("character") - charNameStr, ok := charName.(string) - if !ok || charNameStr == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("Missing character parameter") - return - } - logger.Info("Login requested for character %s", charNameStr) - // Trigger the auth flow (will register callback if needed) - char, err := sso.GetCharacter(context.Background(), charNameStr) - if err != nil { - logger.Error("Failed to authenticate character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Authentication failed") - return - } - logger.Info("Successfully authenticated character %s", charNameStr) - ctx.SetContentType("text/plain") - ctx.SetStatusCode(fasthttp.StatusOK) - ctx.WriteString("Authenticated! Access token: " + char.AccessToken) - }) + // Create route handler with dependencies + routeHandler := routes.NewRouteHandler(sso, webhook, esiClient) + routeHandler.SetupRoutes(r) - // Get planets for a character - r.GET("/planets/{character}", func(ctx *fasthttp.RequestCtx) { - charName := ctx.UserValue("character") - charNameStr, ok := charName.(string) - if !ok || charNameStr == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("Missing character parameter") - return - } - - logger.Info("Fetching planets for character %s", charNameStr) - - // Get access token for character - char, err := sso.GetCharacter(context.Background(), charNameStr) - if err != nil { - logger.Error("Failed to get token for character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Authentication failed") - return - } - - // Fetch planets using ESI API - planets, err := esi.GetCharacterPlanets(context.Background(), int(char.ID), char.AccessToken) - if err != nil { - logger.Error("Failed to fetch planets for character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Failed to fetch planets") - return - } - - // Return planets as JSON - ctx.SetContentType("application/json") - ctx.SetStatusCode(fasthttp.StatusOK) - json.NewEncoder(ctx).Encode(planets) - }) - - // Get detailed planet information - r.GET("/planet/{character}/{planet_id}", func(ctx *fasthttp.RequestCtx) { - charName := ctx.UserValue("character") - charNameStr, ok := charName.(string) - if !ok || charNameStr == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("Missing character parameter") - return - } - - planetIDStr := ctx.UserValue("planet_id") - planetID, err := strconv.Atoi(planetIDStr.(string)) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("Invalid planet ID") - return - } - - logger.Info("Fetching planet details for character %s, planet %d", charNameStr, planetID) - - // Get access token for character - char, err := sso.GetCharacter(context.Background(), charNameStr) - if err != nil { - logger.Error("Failed to get token for character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Authentication failed") - return - } - - // Fetch planet details using ESI API - planetDetail, err := esi.GetPlanetDetails(context.Background(), int(char.ID), planetID, char.AccessToken) - if err != nil { - logger.Error("Failed to fetch planet details for character %s, planet %d: %v", charNameStr, planetID, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Failed to fetch planet details") - return - } - - // Return planet details as JSON - ctx.SetContentType("application/json") - ctx.SetStatusCode(fasthttp.StatusOK) - json.NewEncoder(ctx).Encode(planetDetail) - }) - - // Get all planets with their details for a character - r.GET("/planets-full/{character}", func(ctx *fasthttp.RequestCtx) { - charName := ctx.UserValue("character") - charNameStr, ok := charName.(string) - if !ok || charNameStr == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("Missing character parameter") - return - } - - logger.Info("Fetching full planet data for character %s", charNameStr) - - // Get access token for character - char, err := sso.GetCharacter(context.Background(), charNameStr) - if err != nil { - logger.Error("Failed to get token for character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Authentication failed") - return - } - - // Fetch planets list - planets, err := esi.GetCharacterPlanets(context.Background(), int(char.ID), char.AccessToken) - if err != nil { - logger.Error("Failed to fetch planets for character %s: %v", charNameStr, err) - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.WriteString("Failed to fetch planets") - return - } - - // Fetch details for each planet - type PlanetWithDetails struct { - Planet esi.Planet - Details *esi.PlanetDetail - } - - var planetsWithDetails []PlanetWithDetails - for _, planet := range planets { - details, err := esi.GetPlanetDetails(context.Background(), int(char.ID), planet.PlanetID, char.AccessToken) - if err != nil { - logger.Warning("Failed to fetch details for planet %d: %v", planet.PlanetID, err) - // Continue with other planets even if one fails - details = nil - } - - planetsWithDetails = append(planetsWithDetails, PlanetWithDetails{ - Planet: planet, - Details: details, - }) - } - - // Return planets with details as JSON - ctx.SetContentType("application/json") - ctx.SetStatusCode(fasthttp.StatusOK) - json.NewEncoder(ctx).Encode(planetsWithDetails) - }) - - logger.Info("Starting web server on 0.0.0.0:%s", options.Port) + logger.Info("Starting web server on 0.0.0.0:%s", options.GlobalOptions.Port) go func() { - if err := fasthttp.ListenAndServe("0.0.0.0:"+options.Port, r.Handler); err != nil { + if err := fasthttp.ListenAndServe("0.0.0.0:"+options.GlobalOptions.Port, r.Handler); err != nil { logger.Error("Server failed: %v", err) } }() diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..df2881a --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,229 @@ +package routes + +import ( + "context" + "encoding/json" + "strconv" + "time" + + "go-eve-pi/esi" + wh "go-eve-pi/webhook" + + logger "git.site.quack-lab.dev/dave/cylogger" + "github.com/fasthttp/router" + "github.com/valyala/fasthttp" +) + +// RouteHandler contains dependencies needed for route handlers +type RouteHandler struct { + SSO SSOInterface + Webhook wh.Webhook + ESI esi.ESIInterface +} + +// SSOInterface defines the interface for SSO operations +type SSOInterface interface { + GetCharacter(ctx context.Context, characterName string) (Character, error) +} + +// Character represents a character with authentication info +type Character struct { + ID int64 `gorm:"primaryKey"` + CharacterName string `gorm:"uniqueIndex"` + AccessToken string + RefreshToken string + ExpiresAt time.Time + UpdatedAt time.Time + CreatedAt time.Time +} + +// NewRouteHandler creates a new route handler with dependencies +func NewRouteHandler(sso SSOInterface, webhook wh.Webhook, esi esi.ESIInterface) *RouteHandler { + return &RouteHandler{ + SSO: sso, + Webhook: webhook, + ESI: esi, + } +} + +// SetupRoutes configures all routes on the given router +func (rh *RouteHandler) SetupRoutes(r *router.Router) { + // Root route + r.GET("/", rh.handleRoot) + + // Authentication route + r.GET("/login/{character}", rh.handleLogin) + + // Planet routes + r.GET("/planets/{character}", rh.handlePlanets) + r.GET("/planet/{character}/{planet_id}", rh.handlePlanetDetails) + r.GET("/planets-full/{character}", rh.handlePlanetsFull) +} + +// handleRoot handles the root endpoint +func (rh *RouteHandler) handleRoot(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.WriteString("EVE PI Server Running") +} + +// handleLogin handles character authentication +func (rh *RouteHandler) handleLogin(ctx *fasthttp.RequestCtx) { + charName := ctx.UserValue("character") + charNameStr, ok := charName.(string) + if !ok || charNameStr == "" { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("Missing character parameter") + return + } + logger.Info("Login requested for character %s", charNameStr) + + // Trigger the auth flow (will register callback if needed) + char, err := rh.SSO.GetCharacter(context.Background(), charNameStr) + if err != nil { + logger.Error("Failed to authenticate character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Authentication failed") + return + } + logger.Info("Successfully authenticated character %s", charNameStr) + ctx.SetContentType("text/plain") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.WriteString("Authenticated! Access token: " + char.AccessToken) +} + +// handlePlanets handles fetching planets for a character +func (rh *RouteHandler) handlePlanets(ctx *fasthttp.RequestCtx) { + charName := ctx.UserValue("character") + charNameStr, ok := charName.(string) + if !ok || charNameStr == "" { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("Missing character parameter") + return + } + + logger.Info("Fetching planets for character %s", charNameStr) + + // Get access token for character + char, err := rh.SSO.GetCharacter(context.Background(), charNameStr) + if err != nil { + logger.Error("Failed to get token for character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Authentication failed") + return + } + + // Fetch planets using ESI API + planets, err := rh.ESI.GetCharacterPlanets(context.Background(), int(char.ID), char.AccessToken) + if err != nil { + logger.Error("Failed to fetch planets for character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Failed to fetch planets") + return + } + + // Return planets as JSON + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + json.NewEncoder(ctx).Encode(planets) +} + +// handlePlanetDetails handles fetching detailed planet information +func (rh *RouteHandler) handlePlanetDetails(ctx *fasthttp.RequestCtx) { + charName := ctx.UserValue("character") + charNameStr, ok := charName.(string) + if !ok || charNameStr == "" { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("Missing character parameter") + return + } + + planetIDStr := ctx.UserValue("planet_id") + planetID, err := strconv.Atoi(planetIDStr.(string)) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("Invalid planet ID") + return + } + + logger.Info("Fetching planet details for character %s, planet %d", charNameStr, planetID) + + // Get access token for character + char, err := rh.SSO.GetCharacter(context.Background(), charNameStr) + if err != nil { + logger.Error("Failed to get token for character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Authentication failed") + return + } + + // Fetch planet details using ESI API + planetDetail, err := rh.ESI.GetPlanetDetails(context.Background(), int(char.ID), planetID, char.AccessToken) + if err != nil { + logger.Error("Failed to fetch planet details for character %s, planet %d: %v", charNameStr, planetID, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Failed to fetch planet details") + return + } + + // Return planet details as JSON + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + json.NewEncoder(ctx).Encode(planetDetail) +} + +// handlePlanetsFull handles fetching all planets with their details for a character +func (rh *RouteHandler) handlePlanetsFull(ctx *fasthttp.RequestCtx) { + charName := ctx.UserValue("character") + charNameStr, ok := charName.(string) + if !ok || charNameStr == "" { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("Missing character parameter") + return + } + + logger.Info("Fetching full planet data for character %s", charNameStr) + + // Get access token for character + char, err := rh.SSO.GetCharacter(context.Background(), charNameStr) + if err != nil { + logger.Error("Failed to get token for character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Authentication failed") + return + } + + // Fetch planets list + planets, err := rh.ESI.GetCharacterPlanets(context.Background(), int(char.ID), char.AccessToken) + if err != nil { + logger.Error("Failed to fetch planets for character %s: %v", charNameStr, err) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + ctx.WriteString("Failed to fetch planets") + return + } + + // Fetch details for each planet + type PlanetWithDetails struct { + Planet esi.Planet + Details *esi.PlanetDetail + } + + var planetsWithDetails []PlanetWithDetails + for _, planet := range planets { + details, err := rh.ESI.GetPlanetDetails(context.Background(), int(char.ID), planet.PlanetID, char.AccessToken) + if err != nil { + logger.Warning("Failed to fetch details for planet %d: %v", planet.PlanetID, err) + // Continue with other planets even if one fails + details = nil + } + + planetsWithDetails = append(planetsWithDetails, PlanetWithDetails{ + Planet: planet, + Details: details, + }) + } + + // Return planets with details as JSON + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + json.NewEncoder(ctx).Encode(planetsWithDetails) +}