package api import ( "context" "errors" "fmt" "git.itzana.me/StrafesNET/dev-service/pkg/api/handlers" "git.itzana.me/StrafesNET/dev-service/pkg/api/middleware" "git.itzana.me/StrafesNET/dev-service/pkg/authz" "git.itzana.me/StrafesNET/dev-service/pkg/datastore" "git.itzana.me/StrafesNET/dev-service/pkg/ratelimit" "git.itzana.me/StrafesNET/dev-service/web" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" "net/http" "time" ) // Option defines a function that configures a Router type Option func(*RouterConfig) // RouterConfig holds all router configuration type RouterConfig struct { port int context *cli.Context store datastore.Datastore auth *authz.Service limiter *ratelimit.RateLimit shutdownTimeout time.Duration } // WithPort sets the port for the server func WithPort(port int) Option { return func(cfg *RouterConfig) { cfg.port = port } } // WithContext sets the context for the server func WithContext(ctx *cli.Context) Option { return func(cfg *RouterConfig) { cfg.context = ctx } } // WithDatastore sets the datastore for the router func WithDatastore(store datastore.Datastore) Option { return func(cfg *RouterConfig) { cfg.store = store } } // WithShutdownTimeout sets the graceful shutdown timeout func WithShutdownTimeout(timeout time.Duration) Option { return func(cfg *RouterConfig) { cfg.shutdownTimeout = timeout } } // WithRateLimit sets the rate limiter for the router func WithRateLimit(limiter *ratelimit.RateLimit) Option { return func(cfg *RouterConfig) { cfg.limiter = limiter } } // WithAuth sets the auth for the router func WithAuth(auth *authz.Service) Option { return func(cfg *RouterConfig) { cfg.auth = auth } } func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) { r := gin.Default() r.ForwardedByClientIP = true r.Use(gin.Logger()) r.Use(gin.Recovery()) handlerOptions := []handlers.HandlerOption{ handlers.WithDatastore(cfg.store), handlers.WithAuthService(cfg.auth), handlers.WithRatelimiter(cfg.limiter), } userHandler, err := handlers.NewUserHandler(handlerOptions...) if err != nil { log.WithError(err).Fatal("failed to create user handler") return nil, err } applicationHandler, err := handlers.NewApplicationHandler(handlerOptions...) if err != nil { log.WithError(err).Fatal("failed to create application handler") return nil, err } configHandler, err := handlers.NewConfigHandler(handlerOptions...) if err != nil { log.WithError(err).Fatal("failed to create config handler") return nil, err } v1 := r.Group("/api") { // Middleware v1.Use(middleware.UserSession(cfg.auth)) // Config endpoints v1.GET("/config", configHandler.GetConfig) // User endpoints v1.GET("/user", userHandler.GetCurrentUser) v1.GET("/user/roles", userHandler.GetCurrentRoles) // Application endpoints v1.GET("/application", applicationHandler.GetMyApplications) v1.POST("/application", applicationHandler.CreateApplication) v1.PUT("/application/:id", applicationHandler.UpdateApplication) v1.DELETE("/application/:id", applicationHandler.DeleteApplication) // Admin endpoints (protected by middleware) adminHandler, err := handlers.NewAdminHandler(handlerOptions...) if err != nil { log.WithError(err).Fatal("failed to create admin handler") return nil, err } admin := v1.Group("/admin", middleware.RequireAdmin(cfg.auth)) { // User management admin.GET("/user", adminHandler.ListUsers) admin.GET("/user/:id", adminHandler.GetUser) admin.PUT("/user/:id", adminHandler.UpdateUser) admin.POST("/user/:id/permission/:permId", adminHandler.AddUserPermission) admin.DELETE("/user/:id/permission/:permId", adminHandler.RemoveUserPermission) admin.GET("/user/:id/application", adminHandler.GetUserApplications) // Permission management admin.GET("/permission", adminHandler.ListPermissions) admin.POST("/permission", adminHandler.CreatePermission) admin.PUT("/permission/:id", adminHandler.UpdatePermission) admin.DELETE("/permission/:id", adminHandler.DeletePermission) admin.PATCH("/permission/:id/default", adminHandler.SetPermissionDefault) // Rate limit management admin.GET("/rate-limit", adminHandler.ListRateLimits) admin.POST("/rate-limit", adminHandler.CreateRateLimit) admin.PUT("/rate-limit/:id", adminHandler.UpdateRateLimit) admin.DELETE("/rate-limit/:id", adminHandler.DeleteRateLimit) } } r.Use(middleware.RedirectIfNotLoggedIn(cfg.auth)) web.AddRoutes(r) return r, nil } // NewRouter creates a new router with the given options func NewRouter(options ...Option) error { // Default configuration cfg := &RouterConfig{ port: 8080, // Default port context: nil, shutdownTimeout: 5 * time.Second, } // Apply options for _, option := range options { option(cfg) } // Validate configuration if cfg.context == nil { return errors.New("context is required") } if cfg.store == nil { return errors.New("datastore is required") } if cfg.auth == nil { return errors.New("auth service is required") } if cfg.limiter == nil { return errors.New("rate limiter is required") } routes, err := setupRoutes(cfg) if err != nil { return err } log.Info("Starting server") return runServer(cfg.context.Context, fmt.Sprint(":", cfg.port), routes, cfg.shutdownTimeout) } func runServer(ctx context.Context, addr string, r *gin.Engine, shutdownTimeout time.Duration) error { srv := &http.Server{ Addr: addr, Handler: r, } // Run the server in a separate goroutine go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.WithError(err).Fatal("web server exit") } }() // Wait for a shutdown signal <-ctx.Done() // Shutdown server gracefully ctxShutdown, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() return srv.Shutdown(ctxShutdown) }