package fiber import ( "errors" "html/template" "strconv" "time" cache "git.dmitriygnatenko.ru/dima/go-common/cache/ttl_memory_cache" "git.dmitriygnatenko.ru/dima/go-common/db" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/basicauth" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/monitor" "github.com/gofiber/fiber/v2/middleware/recover" jwt "github.com/gofiber/jwt/v3" "github.com/gofiber/template/html/v2" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/middleware/language" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/repositories" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/auth" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/config" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler" adminHandler "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler/admin" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/i18n" ) const ( appName = "dmitriygnatenko" templatesPath = "./../../internal/templates" staticPath = "../../web" metricsURI = "/metrics" ) type ServiceProvider interface { ConfigService() *config.Service AuthService() *auth.Service CacheService() *cache.Cache TransactionManager() *db.TxManager ArticleRepository() *repositories.ArticleRepository TagRepository() *repositories.TagRepository ArticleTagRepository() *repositories.ArticleTagRepository UserRepository() *repositories.UserRepository } func Init(sp ServiceProvider) (*fiber.App, error) { fiberApp := fiber.New(getConfig(sp)) // Configure web root fiberApp.Static("/", staticPath) // Configure CORS middleware fiberApp.Use(cors.New(getCORSConfig(sp))) // Configure recover middleware fiberApp.Use(recover.New()) // Configure language middleware fiberApp.Use(language.New()) // Configure JWT auth jwtAuth := jwt.New(getJWTConfig(sp)) // Configure Basic auth basicAuth := basicauth.New(basicauth.Config{ Users: map[string]string{ sp.ConfigService().BasicAuthUser(): sp.ConfigService().BasicAuthPassword(), }, }) // Metrics fiberApp.Get(metricsURI, basicAuth, monitor.New(getMetricsConfig())) // Public handlers fiberApp.Get( "/", handler.MainPageHandler( sp.CacheService(), sp.ArticleRepository(), ), ) fiberApp.Get( "/tag/:tag", handler.TagHandler( sp.CacheService(), sp.ArticleRepository(), sp.TagRepository(), ), ) fiberApp.Get( "/article/:article", handler.ArticleHandler( sp.CacheService(), sp.ArticleRepository(), sp.TagRepository(), ), ) // Protected handlers admin := fiberApp.Group("/admin", jwtAuth) admin.Get( "/", adminHandler.ArticleHandler(sp.ArticleRepository()), ) admin.All("/login", limiter.New(limiter.Config{ Max: int(sp.ConfigService().LoginRateLimiterMaxRequests()), Expiration: sp.ConfigService().LoginRateLimiterExpiration(), }), adminHandler.LoginHandler( sp.ConfigService(), sp.AuthService(), sp.UserRepository(), )) admin.All( "/logout", adminHandler.LogoutHandler(sp.ConfigService()), ) admin.All( "/user/change-password", adminHandler.ChangePassword( sp.AuthService(), sp.UserRepository(), ), ) admin.All( "/article/add", adminHandler.AddArticleHandler( sp.TransactionManager(), sp.ArticleRepository(), sp.TagRepository(), sp.ArticleTagRepository(), ), ) admin.All( "/article/edit/:id", adminHandler.EditArticleHandler( sp.TransactionManager(), sp.ArticleRepository(), sp.TagRepository(), sp.ArticleTagRepository(), sp.CacheService(), ), ) admin.All( "/article/delete/:id", adminHandler.DeleteArticleHandler( sp.TransactionManager(), sp.ArticleRepository(), sp.ArticleTagRepository(), sp.CacheService(), ), ) admin.Get( "/tag", adminHandler.TagHandler(sp.TagRepository()), ) admin.All( "/tag/add", adminHandler.AddTagHandler( sp.TagRepository(), ), ) admin.All( "/tag/edit/:id", adminHandler.EditTagHandler( sp.TagRepository(), sp.CacheService(), ), ) admin.All( "/tag/delete/:id", adminHandler.DeleteTagHandler( sp.TagRepository(), sp.CacheService(), ), ) return fiberApp, nil } func getConfig(sp ServiceProvider) fiber.Config { return fiber.Config{ AppName: appName, DisableStartupMessage: true, Views: getViewsEngine(sp), ErrorHandler: getErrorHandler(), } } // nolint func getJWTConfig(sp ServiceProvider) jwt.Config { return jwt.Config{ SigningKey: []byte(sp.ConfigService().JWTSecretKey()), TokenLookup: "cookie:" + sp.ConfigService().JWTCookie(), ErrorHandler: func(fctx *fiber.Ctx, err error) error { return fctx.Redirect("/admin/login") }, Filter: func(fctx *fiber.Ctx) bool { method := fctx.Method() path := fctx.Path() if method != fiber.MethodGet && method != fiber.MethodPost && method != fiber.MethodPut && method != fiber.MethodDelete { return true } if path == "/admin/login" { return true } return false }, } } func getMetricsConfig() monitor.Config { return monitor.Config{ Title: "dmitriygnatenko.ru metrics", } } func getCORSConfig(sp ServiceProvider) cors.Config { return cors.Config{ AllowOrigins: sp.ConfigService().CORSAllowOrigins(), AllowMethods: sp.ConfigService().CORSAllowMethods(), } } func getViewsEngine(sp ServiceProvider) *html.Engine { engine := html.New(templatesPath, ".html") // nolint:gocritic engine.AddFunc("now", func() time.Time { return time.Now() }) // nolint:gosec engine.AddFunc("noescape", func(str string) template.HTML { return template.HTML(str) }) engine.AddFunc("gridsep", func(i, l int) bool { i++ return i%3 == 0 && i != l }) engine.AddFunc("version", func() string { return strconv.FormatUint(uint64(sp.ConfigService().StaticVersion()), 10) }) return engine } func getErrorHandler() fiber.ErrorHandler { return func(fctx *fiber.Ctx, err error) error { lang := i18n.Language(fctx) errCode := fiber.StatusInternalServerError var e *fiber.Error if errors.As(err, &e) { errCode = e.Code } var renderData fiber.Map if errCode == fiber.StatusNotFound { renderData = fiber.Map{ "pageTitle": i18n.T(lang, "page_not_found_err_title"), "code": fiber.StatusNotFound, "text": i18n.T(lang, "page_not_found_err_desc"), } } else { renderData = fiber.Map{ "pageTitle": i18n.T(lang, "internal_err_title"), "code": fiber.StatusInternalServerError, "text": i18n.T(lang, "internal_err_desc"), } } renderData["headTitle"] = i18n.T(lang, "head_title") return fctx.Render("error", renderData, "_layout") } }