package router import ( "errors" "io/fs" "log/slog" "net/http" "code.capytal.cc/capytal/comicverse/service" "code.capytal.cc/capytal/comicverse/templates" "code.capytal.cc/loreddev/smalltrip" "code.capytal.cc/loreddev/smalltrip/middleware" "code.capytal.cc/loreddev/smalltrip/multiplexer" "code.capytal.cc/loreddev/smalltrip/problem" "code.capytal.cc/loreddev/x/tinyssert" ) type router struct { userService *service.User tokenService *service.Token publicationService *service.Publication templates templates.ITemplate assets fs.FS cache bool assert tinyssert.Assertions log *slog.Logger } func New(cfg Config) (http.Handler, error) { if cfg.UserService == nil { return nil, errors.New("user service is nil") } if cfg.TokenService == nil { return nil, errors.New("token service is nil") } if cfg.PublicationService == nil { return nil, errors.New("publication service is nil") } if cfg.Templates == nil { return nil, errors.New("templates is nil") } if cfg.Assets == nil { return nil, errors.New("static files is nil") } if cfg.Assertions == nil { return nil, errors.New("assertions is nil") } if cfg.Logger == nil { return nil, errors.New("logger is nil") } r := &router{ userService: cfg.UserService, tokenService: cfg.TokenService, publicationService: cfg.PublicationService, templates: cfg.Templates, assets: cfg.Assets, cache: !cfg.DisableCache, assert: cfg.Assertions, log: cfg.Logger, } return r.setup(), nil } type Config struct { UserService *service.User TokenService *service.Token PublicationService *service.Publication Templates templates.ITemplate Assets fs.FS DisableCache bool Assertions tinyssert.Assertions Logger *slog.Logger } func (router *router) setup() http.Handler { router.assert.NotNil(router.log) router.assert.NotNil(router.assets) log := router.log log.Debug("Initializing router") mux := multiplexer.New() mux = multiplexer.WithFormMethod(mux, "x-method") mux = multiplexer.WithPatternRules(mux, multiplexer.EnsureMethod(), multiplexer.EnsureStrictEnd(), multiplexer.EnsureTrailingSlash(), ) r := smalltrip.NewRouter( smalltrip.WithMultiplexer(mux), smalltrip.WithLogger(log.WithGroup("smalltrip")), ) r.Use(middleware.Logger(log.WithGroup("requests"))) if router.cache { r.Use(middleware.Cache()) } else { r.Use(middleware.DisableCache()) } r.Use(problem.PanicMiddleware()) // TODO: when the HandlerDevpage is completed on the problem package, we // will provide it a custom template here: // r.Use(problem.Middleware()) userController := newUserController(userControllerCfg{ UserService: router.userService, TokenService: router.tokenService, LoginPath: "/login/", RedirectPath: "/", Templates: router.templates, Assert: router.assert, }) publicationController := newPublicationController(router.publicationService, router.templates, router.assert) r.Handle("GET /assets/{_file...}", http.StripPrefix("/assets/", http.FileServerFS(router.assets))) r.Use(userController.userMiddleware) r.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { // TODO: Add a way to the user to bypass this check and see the landing page. // Probably a query parameter to bypass like "?landing=true" if _, ok := NewUserContext(r.Context()).GetUserID(); ok { publicationController.dashboard(w, r) return } err := router.templates.ExecuteTemplate(w, "landing", nil) if err != nil { problem.NewInternalServerError(err).ServeHTTP(w, r) } }) r.HandleFunc("GET /login/{$}", userController.login) r.HandleFunc("POST /login/{$}", userController.login) r.HandleFunc("GET /register/{$}", userController.register) r.HandleFunc("POST /register/{$}", userController.register) // TODO: Provide/redirect short publication-id paths to long paths with the publication title as URL /publications/title-of-the-publication- r.HandleFunc("GET /publication/{publicationID}/{$}", publicationController.getPublication) r.HandleFunc("POST /publication/{$}", publicationController.createPublication) return r }