package main import ( "flag" "fmt" "log" "os" "os/signal" "path/filepath" "syscall" "dk/internal/auth" "dk/internal/csrf" "dk/internal/middleware" "dk/internal/models/babble" "dk/internal/models/control" "dk/internal/models/drops" "dk/internal/models/forum" "dk/internal/models/items" "dk/internal/models/monsters" "dk/internal/models/news" "dk/internal/models/spells" "dk/internal/models/towns" "dk/internal/models/users" "dk/internal/router" "dk/internal/routes" "dk/internal/session" "dk/internal/template" "github.com/valyala/fasthttp" ) func main() { var port string flag.StringVar(&port, "p", "3000", "Port to run server on") if len(os.Args) < 2 { startServer(port) return } switch os.Args[1] { case "serve": flag.CommandLine.Parse(os.Args[2:]) startServer(port) default: fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1]) fmt.Fprintln(os.Stderr, "Available commands:") fmt.Fprintln(os.Stderr, " serve - Start the server") fmt.Fprintln(os.Stderr, " (no command) - Start the server") os.Exit(1) } } func loadModels() error { dataDir := "data" if err := os.MkdirAll(dataDir, 0755); err != nil { return fmt.Errorf("failed to create data directory: %w", err) } if err := users.LoadData(filepath.Join(dataDir, "users.json")); err != nil { return fmt.Errorf("failed to load users data: %w", err) } if err := towns.LoadData(filepath.Join(dataDir, "towns.json")); err != nil { return fmt.Errorf("failed to load towns data: %w", err) } if err := spells.LoadData(filepath.Join(dataDir, "spells.json")); err != nil { return fmt.Errorf("failed to load spells data: %w", err) } if err := news.LoadData(filepath.Join(dataDir, "news.json")); err != nil { return fmt.Errorf("failed to load news data: %w", err) } if err := monsters.LoadData(filepath.Join(dataDir, "monsters.json")); err != nil { return fmt.Errorf("failed to load monsters data: %w", err) } if err := items.LoadData(filepath.Join(dataDir, "items.json")); err != nil { return fmt.Errorf("failed to load items data: %w", err) } if err := forum.LoadData(filepath.Join(dataDir, "forum.json")); err != nil { return fmt.Errorf("failed to load forum data: %w", err) } if err := drops.LoadData(filepath.Join(dataDir, "drops.json")); err != nil { return fmt.Errorf("failed to load drops data: %w", err) } if err := babble.LoadData(filepath.Join(dataDir, "babble.json")); err != nil { return fmt.Errorf("failed to load babble data: %w", err) } if err := control.Load(filepath.Join(dataDir, "control.json")); err != nil { return fmt.Errorf("failed to load control data: %w", err) } return nil } func saveModels() error { dataDir := "data" if err := users.SaveData(filepath.Join(dataDir, "users.json")); err != nil { return fmt.Errorf("failed to save users data: %w", err) } if err := towns.SaveData(filepath.Join(dataDir, "towns.json")); err != nil { return fmt.Errorf("failed to save towns data: %w", err) } if err := spells.SaveData(filepath.Join(dataDir, "spells.json")); err != nil { return fmt.Errorf("failed to save spells data: %w", err) } if err := news.SaveData(filepath.Join(dataDir, "news.json")); err != nil { return fmt.Errorf("failed to save news data: %w", err) } if err := monsters.SaveData(filepath.Join(dataDir, "monsters.json")); err != nil { return fmt.Errorf("failed to save monsters data: %w", err) } if err := items.SaveData(filepath.Join(dataDir, "items.json")); err != nil { return fmt.Errorf("failed to save items data: %w", err) } if err := forum.SaveData(filepath.Join(dataDir, "forum.json")); err != nil { return fmt.Errorf("failed to save forum data: %w", err) } if err := drops.SaveData(filepath.Join(dataDir, "drops.json")); err != nil { return fmt.Errorf("failed to save drops data: %w", err) } if err := babble.SaveData(filepath.Join(dataDir, "babble.json")); err != nil { return fmt.Errorf("failed to save babble data: %w", err) } if err := control.Save(); err != nil { return fmt.Errorf("failed to save control data: %w", err) } return nil } func startServer(port string) { fmt.Println("Starting Dragon Knight server...") if err := start(port); err != nil { log.Fatalf("Server failed: %v", err) } } func start(port string) error { cwd, err := os.Getwd() if err != nil { return fmt.Errorf("failed to get current working directory: %w", err) } template.InitializeCache(cwd) if err := loadModels(); err != nil { return fmt.Errorf("failed to load models: %w", err) } session.Init("data/_sessions.json") r := router.New() r.Use(middleware.Timing()) r.Use(auth.Middleware()) r.Use(csrf.Middleware()) r.Get("/", routes.Index) r.WithMiddleware(auth.RequireAuth()).Get("/explore", routes.Explore) r.WithMiddleware(auth.RequireAuth()).Post("/move", routes.Move) routes.RegisterAuthRoutes(r) routes.RegisterTownRoutes(r) // Use current working directory for static files assetsDir := filepath.Join(cwd, "assets") // Static file server for /assets fs := &fasthttp.FS{ Root: assetsDir, Compress: false, } assetsHandler := fs.NewRequestHandler() // Combined handler requestHandler := func(ctx *fasthttp.RequestCtx) { path := string(ctx.Path()) // Handle static assets - strip /assets prefix if len(path) >= 7 && path[:7] == "/assets" { // Strip the /assets prefix for the file system handler originalPath := ctx.Path() ctx.Request.URI().SetPath(path[7:]) // Remove "/assets" prefix assetsHandler(ctx) ctx.Request.URI().SetPathBytes(originalPath) // Restore original path return } // Handle routes r.ServeHTTP(ctx) } addr := ":" + port log.Printf("Server starting on %s", addr) // Setup graceful shutdown server := &fasthttp.Server{ Handler: requestHandler, } // Channel to listen for interrupt signal c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // Start server in a goroutine go func() { if err := server.ListenAndServe(addr); err != nil { log.Printf("Server error: %v", err) } }() // Wait for interrupt signal <-c log.Println("Received shutdown signal, shutting down gracefully...") // Save all model data before shutdown log.Println("Saving model data...") if err := saveModels(); err != nil { log.Printf("Error saving model data: %v", err) } // Save sessions before shutdown log.Println("Saving sessions...") if err := session.Close(); err != nil { log.Printf("Error saving sessions: %v", err) } // FastHTTP doesn't have a graceful Shutdown method like net/http // We just let the server stop naturally when the main function exits log.Println("Server stopped") return nil }