package fiber import ( "errors" "html/template" "strconv" "time" cacheService "git.dmitriygnatenko.ru/dima/go-common/cache/ttl_memory_cache" "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" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/repositories" authService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/auth" envService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/env" "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler" adminHandler "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler/admin" ) const ( appName = "dmitriygnatenko" templatesPath = "./../../internal/templates" staticPath = "../../web" metricsURI = "/metrics" loginRateLimiterMaxRequests = 10 loginRateLimiterExpiration = 30 * time.Second ) type ( ServiceProvider interface { EnvService() *envService.Service AuthService() *authService.Service CacheService() *cacheService.Cache ArticleRepository() *repositories.ArticleRepository TagRepository() *repositories.TagRepository ArticleTagRepository() *repositories.ArticleTagRepository UserRepository() *repositories.UserRepository } EnvService interface { StaticVersion() uint64 } ) 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 JWT auth jwtAuth := jwt.New(getJWTConfig(sp)) // Configure Basic auth basicAuth := basicauth.New(basicauth.Config{ Users: map[string]string{ sp.EnvService().BasicAuthUser(): sp.EnvService().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: loginRateLimiterMaxRequests, Expiration: loginRateLimiterExpiration, }), adminHandler.LoginHandler( sp.EnvService(), sp.AuthService(), sp.UserRepository(), )) admin.All( "/logout", adminHandler.LogoutHandler(sp.EnvService()), ) admin.All( "/user/change-password", adminHandler.ChangePassword( sp.AuthService(), sp.UserRepository(), ), ) admin.All( "/article/add", adminHandler.AddArticleHandler( sp.ArticleRepository(), sp.TagRepository(), sp.ArticleTagRepository(), ), ) admin.All( "/article/edit/:id", adminHandler.EditArticleHandler( sp.ArticleRepository(), sp.TagRepository(), sp.ArticleTagRepository(), sp.CacheService(), ), ) admin.All( "/article/delete/:id", adminHandler.DeleteArticleHandler( sp.ArticleRepository(), sp.ArticleTagRepository(), sp.CacheService(), ), ) admin.Get( "/tag", adminHandler.TagHandler(sp.TagRepository()), ) admin.All( "/tag/add", adminHandler.AddTagHandler( sp.TagRepository(), sp.CacheService(), ), ) 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.EnvService()), ErrorHandler: getErrorHandler(), } } // nolint func getJWTConfig(sp ServiceProvider) jwt.Config { return jwt.Config{ SigningKey: []byte(sp.EnvService().JwtSecretKey()), TokenLookup: "cookie:" + sp.EnvService().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.EnvService().AppCORSAllowOrigins(), AllowMethods: sp.EnvService().AppCORSAllowMethods(), } } func getViewsEngine(env EnvService) *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(env.StaticVersion(), 64) }) return engine } func getErrorHandler() fiber.ErrorHandler { return func(fctx *fiber.Ctx, err error) error { 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": "Страница не найдена", "code": fiber.StatusNotFound, "text": "Запрашиваемая вами страница не найдена", } } else { renderData = fiber.Map{ "pageTitle": "Внутренняя ошибка", "code": fiber.StatusInternalServerError, "text": "Внутренняя ошибка сервера, идем исправлять...", } } renderData["headTitle"] = "От слона к суслику - статьи про PHP, Go, алгоритмы" return fctx.Render("error", renderData, "_layout") } }