163 lines
4.1 KiB
Go

package server
import (
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"dk/internal/auth"
"dk/internal/database"
"dk/internal/middleware"
"dk/internal/router"
"dk/internal/routes"
"dk/internal/template"
"dk/internal/template/components"
"github.com/valyala/fasthttp"
)
func Start(port string) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current working directory: %w", err)
}
// Initialize template singleton
template.InitializeCache(cwd)
// Initialize database singleton
if err := database.InitializeDB("dk.db"); err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
}
defer database.Close()
auth.Init("sessions.json") // Initialize auth.Manager
r := router.New()
r.Use(middleware.Timing())
r.Use(middleware.Auth(auth.Manager))
r.Use(middleware.CSRF(auth.Manager))
// Setup route handlers
routes.RegisterAuthRoutes(r)
// Dashboard (protected route)
r.WithMiddleware(middleware.RequireAuth("/login")).Get("/dashboard", func(ctx router.Ctx, params []string) {
currentUser := middleware.GetCurrentUser(ctx)
totalSessions, activeSessions := auth.Manager.SessionStats()
pageData := components.NewPageData(
"Dashboard - Dragon Knight",
fmt.Sprintf("Welcome back, %s!", currentUser.Username),
)
additionalData := map[string]any{
"total_sessions": totalSessions,
"active_sessions": activeSessions,
"authenticated": true,
"username": currentUser.Username,
}
if err := components.RenderPage(ctx, pageData, additionalData); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
}
})
// Hello world endpoint (public)
r.Get("/", func(ctx router.Ctx, params []string) {
// Get current user if authenticated
currentUser := middleware.GetCurrentUser(ctx)
var username string
if currentUser != nil {
username = currentUser.Username
} else {
username = "Guest"
}
totalSessions, activeSessions := auth.Manager.SessionStats()
pageData := components.NewPageData(
"Dragon Knight",
fmt.Sprintf("Hello %s!", username),
)
additionalData := map[string]any{
"total_sessions": totalSessions,
"active_sessions": activeSessions,
"authenticated": currentUser != nil,
"username": username,
}
if err := components.RenderPage(ctx, pageData, additionalData); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Template error: %v", err)
return
}
})
// 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 sessions before shutdown
log.Println("Saving sessions...")
if err := auth.Manager.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
}