177 lines
4.5 KiB
Go
177 lines
4.5 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"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
func Start(port string) error {
|
|
// Initialize template cache - use current working directory for development
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current working directory: %w", err)
|
|
}
|
|
templateCache := template.NewCache(cwd)
|
|
|
|
// Initialize database
|
|
db, err := database.Open("dk.db")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Initialize authentication manager
|
|
authManager := auth.NewAuthManager(db, "sessions.json")
|
|
// Don't defer Close() here - we'll handle it in shutdown
|
|
|
|
// Initialize router
|
|
r := router.New()
|
|
|
|
// Add middleware
|
|
r.Use(middleware.Timing())
|
|
r.Use(middleware.Auth(authManager))
|
|
r.Use(middleware.CSRF(authManager))
|
|
|
|
// Setup route handlers
|
|
routes.RegisterAuthRoutes(r, authManager, templateCache)
|
|
|
|
// Dashboard (protected route)
|
|
r.WithMiddleware(middleware.RequireAuth("/login")).Get("/dashboard", func(ctx router.Ctx, params []string) {
|
|
tmpl, err := templateCache.Load("layout.html")
|
|
if err != nil {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
fmt.Fprintf(ctx, "Template error: %v", err)
|
|
return
|
|
}
|
|
|
|
currentUser := middleware.GetCurrentUser(ctx)
|
|
totalSessions, activeSessions := authManager.SessionStats()
|
|
|
|
data := map[string]any{
|
|
"title": "Dashboard - Dragon Knight",
|
|
"content": fmt.Sprintf("Welcome back, %s!", currentUser.Username),
|
|
"totaltime": middleware.GetRequestTime(ctx),
|
|
"numqueries": "0",
|
|
"version": "1.0.0",
|
|
"build": "dev",
|
|
"total_sessions": totalSessions,
|
|
"active_sessions": activeSessions,
|
|
"authenticated": true,
|
|
"username": currentUser.Username,
|
|
}
|
|
|
|
tmpl.WriteTo(ctx, data)
|
|
})
|
|
|
|
// Hello world endpoint (public)
|
|
r.Get("/", func(ctx router.Ctx, params []string) {
|
|
tmpl, err := templateCache.Load("layout.html")
|
|
if err != nil {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
fmt.Fprintf(ctx, "Template error: %v", err)
|
|
return
|
|
}
|
|
|
|
// Get current user if authenticated
|
|
currentUser := middleware.GetCurrentUser(ctx)
|
|
var username string
|
|
if currentUser != nil {
|
|
username = currentUser.Username
|
|
} else {
|
|
username = "Guest"
|
|
}
|
|
|
|
totalSessions, activeSessions := authManager.SessionStats()
|
|
|
|
data := map[string]any{
|
|
"title": "Dragon Knight",
|
|
"content": fmt.Sprintf("Hello %s!", username),
|
|
"totaltime": middleware.GetRequestTime(ctx),
|
|
"numqueries": "0", // Placeholder for now
|
|
"version": "1.0.0",
|
|
"build": "dev",
|
|
"total_sessions": totalSessions,
|
|
"active_sessions": activeSessions,
|
|
"authenticated": currentUser != nil,
|
|
"username": username,
|
|
}
|
|
|
|
tmpl.WriteTo(ctx, data)
|
|
})
|
|
|
|
// 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 := authManager.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
|
|
}
|