package fiber import ( "html/template" "log" "strconv" "time" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/interfaces" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler" adminHandler "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler/admin" "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" ) const ( appName = "dmitriygnatenko" templatesPath = "./../../internal/templates" staticPath = "../../web" metricsURI = "/metrics" loginRateLimiterMaxRequests = 10 loginRateLimiterExpiration = 30 * time.Second ) func Init(sp interfaces.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 JWT auth jwtAuth := jwt.New(getJWTConfig(sp)) // Configure Basic auth basicAuth := basicauth.New(basicauth.Config{ Users: map[string]string{ sp.GetEnvService().GetBasicAuthUser(): sp.GetEnvService().GetBasicAuthPassword(), }, }) // Metrics fiberApp.Get(metricsURI, basicAuth, monitor.New(getMetricsConfig())) // Public handlers fiberApp.Get("/", handler.MainPageHandler(sp)) fiberApp.Get("/tag/:tag", handler.TagHandler(sp)) fiberApp.Get("/article/:article", handler.ArticleHandler(sp)) // Protected handlers admin := fiberApp.Group("/admin", jwtAuth) admin.Get("/", adminHandler.ArticleHandler(sp)) admin.All("/login", limiter.New(limiter.Config{ Max: loginRateLimiterMaxRequests, Expiration: loginRateLimiterExpiration, }), adminHandler.LoginHandler(sp)) admin.All("/logout", adminHandler.LogoutHandler(sp)) admin.All("/user/change-password", adminHandler.ChangePassword(sp)) admin.All("/article/add", adminHandler.AddArticleHandler(sp)) admin.All("/article/edit/:id", adminHandler.EditArticleHandler(sp)) admin.All("/article/delete/:id", adminHandler.DeleteArticleHandler(sp)) admin.Get("/tag", adminHandler.TagHandler(sp)) admin.All("/tag/add", adminHandler.AddTagHandler(sp)) admin.All("/tag/edit/:id", adminHandler.EditTagHandler(sp)) admin.All("/tag/delete/:id", adminHandler.DeleteTagHandler(sp)) return fiberApp, nil } func getConfig(sp interfaces.ServiceProvider) fiber.Config { return fiber.Config{ AppName: appName, DisableStartupMessage: true, Views: getViewsEngine(sp.GetEnvService()), ErrorHandler: getErrorHandler(sp), } } // nolint func getJWTConfig(sp interfaces.ServiceProvider) jwt.Config { return jwt.Config{ SigningKey: []byte(sp.GetEnvService().GetJWTSecretKey()), TokenLookup: "cookie:" + sp.GetEnvService().GetJWTCookie(), 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 interfaces.ServiceProvider) cors.Config { return cors.Config{ AllowOrigins: sp.GetEnvService().GetCORSAllowOrigins(), AllowMethods: sp.GetEnvService().GetCORSAllowMethods(), } } func getViewsEngine(env interfaces.Env) *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.Itoa(env.GetStaticVersion()) }) engine.AddFunc("ga", func() string { return env.GetGAKey() }) return engine } func getErrorHandler(sp interfaces.ServiceProvider) fiber.ErrorHandler { return func(fctx *fiber.Ctx, err error) error { errCode := fiber.StatusInternalServerError if e, ok := err.(*fiber.Error); ok { errCode = e.Code } if err.Error() != "" { errorsEmail := sp.GetEnvService().GetErrorsEmail() if errCode == fiber.StatusInternalServerError && errorsEmail != "" { log.Println(err) // nolint sp.GetMailerService().Send( errorsEmail, "AUTO - dmitriygnatenko.ru error", err.Error(), ) } } var renderData fiber.Map if errCode == fiber.StatusNotFound { renderData = fiber.Map{ "pageTitle": "Страница не найдена", "code": fiber.StatusNotFound, "text": "Запрашиваемая вами страница не найдена", } } else { renderData = fiber.Map{ "pageTitle": "Внутренняя ошибка", "code": fiber.StatusInternalServerError, "text": "Внутренняя ошибка сервера, идем исправлять...", } } renderData["headTitle"] = "От слона к суслику - статьи про PHP, Go, алгоритмы" return fctx.Render("error", renderData, "_layout") } }