package servecmd import ( "context" "flag" "fmt" "log/slog" "git.maronato.dev/maronato/goshort/cmd/shared" "git.maronato.dev/maronato/goshort/docs" "git.maronato.dev/maronato/goshort/frontend" "git.maronato.dev/maronato/goshort/internal/config" "git.maronato.dev/maronato/goshort/internal/server" apiserver "git.maronato.dev/maronato/goshort/internal/server/api" healthcheckserver "git.maronato.dev/maronato/goshort/internal/server/healthcheck" authmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/auth" oidcserver "git.maronato.dev/maronato/goshort/internal/server/oidc" shortserver "git.maronato.dev/maronato/goshort/internal/server/short" staticssterver "git.maronato.dev/maronato/goshort/internal/server/static" configservice "git.maronato.dev/maronato/goshort/internal/service/config" oidcservice "git.maronato.dev/maronato/goshort/internal/service/oidc" shortservice "git.maronato.dev/maronato/goshort/internal/service/short" shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog" tokenservice "git.maronato.dev/maronato/goshort/internal/service/token" userservice "git.maronato.dev/maronato/goshort/internal/service/user" handlerutils "git.maronato.dev/maronato/goshort/internal/util/handler" "git.maronato.dev/maronato/goshort/internal/util/logging" "github.com/go-chi/chi/v5" "github.com/peterbourgon/ff/v3/ffcli" ) func New(cfg *config.Config) *ffcli.Command { // Create the flagset and register the flags. fs := flag.NewFlagSet("goshort serve", flag.ContinueOnError) shared.RegisterBaseFlags(fs, cfg) shared.RegisterServerFlags(fs, cfg) fs.BoolVar(&cfg.Prod, "prod", config.DefaultProd, "run in production mode") // Create the command and options cmd := shared.NewCommand(cfg, exec) opts := shared.NewSharedCmdOptions() // Return the ffcli command. return &ffcli.Command{ Name: "serve", ShortUsage: "goshort serve [flags]", ShortHelp: "Start the API server with embedded frontend", FlagSet: fs, Exec: cmd.Exec, Options: opts, } } func exec(ctx context.Context, cfg *config.Config) error { l := logging.FromCtx(ctx) l.Debug("Executing serve command", slog.Any("config", cfg)) // Create the new server srv := server.NewServer(cfg) // Create services storage := shared.InitStorage(ctx, cfg) err := storage.Start(ctx) if err != nil { return fmt.Errorf("failed to start storage, %w", err) } defer func() { err := storage.Stop(ctx) if err != nil { l.Error("failed to stop storage", "error", err) } }() shortService := shortservice.NewShortService(storage) userService := userservice.NewUserService(cfg, storage) tokenService := tokenservice.NewTokenService(storage) shortLogService := shortlogservice.NewShortLogService(storage) oidcService := oidcservice.NewOIDCService(ctx, cfg) configService := configservice.NewConfigService(cfg) // Start short log worker stopWorker, _ := shortLogService.StartWorker(ctx) defer stopWorker() // Create handlers apiHandler := apiserver.NewAPIHandler(shortService, userService, tokenService, shortLogService, configService) shortHandler := shortserver.NewShortHandler(shortService, shortLogService) staticHandler := staticssterver.NewStaticHandler(cfg, "/", frontend.Assets()) healthcheckHandler := healthcheckserver.NewHealthcheckHandler(storage) docsHandler := staticssterver.NewStaticHandler(cfg, "/api/docs", docs.Assets()) oidcHandler := oidcserver.NewOIDCHandler(cfg, oidcService, userService) // Create routers apiRouter := apiserver.NewAPIRouter(apiHandler) shortRouter := shortserver.NewShortRouter(shortHandler) staticRouter := staticssterver.NewStaticRouter(staticHandler) healthcheckRouter := healthcheckserver.NewHealthcheckRouter(healthcheckHandler) docsRouter := staticssterver.NewStaticRouter(docsHandler) oidcRouter := oidcserver.NewOIDCRouter(oidcHandler) // Create the root URL handler by chaining short and static routers chainedRouter := handlerutils.NewChainedHandler(shortRouter, staticRouter) // Configure app routes srv.Mux.Route("/api", func(r chi.Router) { r.Use(authmiddleware.Auth(userService, tokenService)) r.Mount("/", apiRouter) r.Mount("/docs", docsRouter) }) // Register OIDC routes if the service is enabled if oidcService != nil { srv.Mux.Mount("/odc", oidcRouter) } srv.Mux.Mount("/healthz", healthcheckRouter) srv.Mux.Mount("/", chainedRouter) if err := srv.ListenAndServe(ctx); err != nil { return fmt.Errorf("rrror during server execution, %w", err) } return nil }