261 lines
6.9 KiB
Go
261 lines
6.9 KiB
Go
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/fights"
|
|
"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(dataDir string) error {
|
|
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)
|
|
}
|
|
|
|
if err := fights.LoadData(filepath.Join(dataDir, "fights.json")); err != nil {
|
|
return fmt.Errorf("failed to load fights data: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func saveModels(dataDir string) error {
|
|
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)
|
|
}
|
|
|
|
if err := fights.SaveData(filepath.Join(dataDir, "fights.json")); err != nil {
|
|
return fmt.Errorf("failed to save fights 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(filepath.Join(cwd, "data")); err != nil {
|
|
return fmt.Errorf("failed to load models: %w", err)
|
|
}
|
|
|
|
session.Init(filepath.Join(cwd, "data/_sessions.json"))
|
|
|
|
r := router.New()
|
|
r.Use(middleware.Timing())
|
|
r.Use(auth.Middleware())
|
|
r.Use(csrf.Middleware())
|
|
|
|
r.Get("/", routes.Index)
|
|
|
|
actions := r.Group("")
|
|
actions.Use(auth.RequireAuth())
|
|
actions.Get("/explore", routes.Explore)
|
|
actions.Post("/move", routes.Move)
|
|
actions.Get("/teleport/:to", routes.Teleport)
|
|
|
|
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(filepath.Join(cwd, "data")); 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
|
|
}
|