package web import ( "context" "log" "os" "os/signal" "strconv" "syscall" "time" "git.ma-al.com/goc_marek/timetracker/app/config" "git.ma-al.com/goc_marek/timetracker/app/delivery/handler" "git.ma-al.com/goc_marek/timetracker/app/delivery/middleware" "git.ma-al.com/goc_marek/timetracker/app/delivery/web/public" // "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v3" // "github.com/gofiber/fiber/v3/middleware/filesystem" "github.com/gofiber/fiber/v3/middleware/logger" "github.com/gofiber/fiber/v3/middleware/recover" ) // Server represents the web server type Server struct { app *fiber.App cfg *config.Config api fiber.Router } // App returns the fiber app func (s *Server) App() *fiber.App { return s.app } // Cfg returns the config func (s *Server) Cfg() *config.Config { return s.cfg } // New creates a new server instance func New() *Server { return &Server{ app: fiber.New(fiber.Config{ ErrorHandler: customErrorHandler, }), cfg: config.Get(), } } // Setup configures the server with routes and middleware func (s *Server) Setup() error { // Global middleware s.app.Use(recover.New()) s.app.Use(logger.New()) // CORS middleware s.app.Use(middleware.CORSMiddleware()) // Language middleware - discovers client's language and stores in context s.app.Use(middleware.LanguageMiddleware()) // initialize healthcheck public.InitHealth(s.App(), s.Cfg()) // serve favicon public.Favicon(s.app, s.cfg) // API routes s.api = s.app.Group("/api/v1") // initialize swagger endpoints public.InitSwagger(s.App()) // Auth routes (public) auth := s.api.Group("/auth") handler.AuthHandlerRoutes(auth) // Repo routes (public) repo := s.api.Group("/repo") repo.Use(middleware.AuthMiddleware()) handler.RepoHandlerRoutes(repo) // Protected routes example protected := s.api.Group("/restricted") protected.Use(middleware.AuthMiddleware()) protected.Get("/dashboard", func(c fiber.Ctx) error { user := middleware.GetUser(c) return c.JSON(fiber.Map{ "message": "Welcome to the protected area", "user": user, }) }) // Admin routes example admin := s.api.Group("/admin") admin.Use(middleware.AuthMiddleware()) admin.Use(middleware.RequireAdmin()) admin.Get("/users", func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Admin area - user management", }) }) public.NewLangHandler().InitLanguage(s.api, s.cfg) // Settings endpoint public.NewSettingsHandler().InitSettings(s.api, s.cfg) // keep this at the end because its wilderange public.InitBo(s.App()) return nil } // Run starts the server func (s *Server) Run() error { // Run database migrations // if err := db.RunMigrations(); err != nil { // log.Printf("⚠️ Database migrations failed: %v", err) // } else { // log.Println("✓ Database migrations completed") // } // // Seed admin user // if err := db.SeedAdminUser("admin@example.com", "admin123"); err != nil { // log.Printf("⚠️ Admin user seeding failed: %v", err) // } addr := s.cfg.Server.Host + ":" + strconv.Itoa(s.cfg.Server.Port) log.Printf("Starting server on %s", addr) log.Printf("Swagger UI available at http://%s/swagger/index.html", addr) log.Printf("OpenAPI JSON available at http://%s/openapi.json", addr) go func() { if err := s.app.Listen(":3000"); err != nil { log.Println("Server stopped:", err) } }() // Wait for shutdown signal quit := make(chan os.Signal, 1) signal.Notify( quit, syscall.SIGINT, // Ctrl+C syscall.SIGTERM, // docker stop ) <-quit log.Println("Shutting down server...") // graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.app.ShutdownWithContext(ctx); err != nil { log.Fatal("Shutdown error:", err) } log.Println("Server exited cleanly") return nil } // customErrorHandler handles errors func customErrorHandler(c fiber.Ctx, err error) error { code := fiber.StatusInternalServerError if e, ok := err.(*fiber.Error); ok { code = e.Code } return c.Status(code).JSON(fiber.Map{ "error": err.Error(), }) }