fiber.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package fiber
  2. import (
  3. "html/template"
  4. "log"
  5. "strconv"
  6. "time"
  7. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/interfaces"
  8. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler"
  9. adminHandler "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler/admin"
  10. "github.com/gofiber/fiber/v2"
  11. "github.com/gofiber/fiber/v2/middleware/basicauth"
  12. "github.com/gofiber/fiber/v2/middleware/cors"
  13. "github.com/gofiber/fiber/v2/middleware/limiter"
  14. "github.com/gofiber/fiber/v2/middleware/monitor"
  15. "github.com/gofiber/fiber/v2/middleware/recover"
  16. jwt "github.com/gofiber/jwt/v3"
  17. "github.com/gofiber/template/html"
  18. )
  19. const (
  20. appName = "dmitriygnatenko"
  21. templatesPath = "./../../internal/templates"
  22. staticPath = "../../web"
  23. metricsURI = "/metrics"
  24. loginRateLimiterMaxRequests = 10
  25. loginRateLimiterExpiration = 30 * time.Second
  26. )
  27. func Init(sp interfaces.ServiceProvider) (*fiber.App, error) {
  28. fiberApp := fiber.New(getConfig(sp))
  29. // Configure web root
  30. fiberApp.Static("/", staticPath)
  31. // Configure CORS middleware
  32. fiberApp.Use(cors.New(getCORSConfig(sp)))
  33. // Configure recover middleware
  34. fiberApp.Use(recover.New())
  35. // Configure JWT auth
  36. jwtAuth := jwt.New(getJWTConfig(sp))
  37. // Configure Basic auth
  38. basicAuth := basicauth.New(basicauth.Config{
  39. Users: map[string]string{
  40. sp.GetEnvService().GetBasicAuthUser(): sp.GetEnvService().GetBasicAuthPassword(),
  41. },
  42. })
  43. // Metrics
  44. fiberApp.Get(metricsURI, basicAuth, monitor.New(getMetricsConfig()))
  45. // Public handlers
  46. fiberApp.Get("/", handler.MainPageHandler(sp))
  47. fiberApp.Get("/tag/:tag", handler.TagHandler(sp))
  48. fiberApp.Get("/article/:article", handler.ArticleHandler(sp))
  49. // Protected handlers
  50. admin := fiberApp.Group("/admin", jwtAuth)
  51. admin.Get("/", adminHandler.ArticleHandler(sp))
  52. admin.All("/login", limiter.New(limiter.Config{
  53. Max: loginRateLimiterMaxRequests,
  54. Expiration: loginRateLimiterExpiration,
  55. }), adminHandler.LoginHandler(sp))
  56. admin.All("/logout", adminHandler.LogoutHandler(sp))
  57. admin.All("/user/change-password", adminHandler.ChangePassword(sp))
  58. admin.All("/article/add", adminHandler.AddArticleHandler(sp))
  59. admin.All("/article/edit/:id<int>", adminHandler.EditArticleHandler(sp))
  60. admin.All("/article/delete/:id<int>", adminHandler.DeleteArticleHandler(sp))
  61. admin.Get("/tag", adminHandler.TagHandler(sp))
  62. admin.All("/tag/add", adminHandler.AddTagHandler(sp))
  63. admin.All("/tag/edit/:id<int>", adminHandler.EditTagHandler(sp))
  64. admin.All("/tag/delete/:id<int>", adminHandler.DeleteTagHandler(sp))
  65. return fiberApp, nil
  66. }
  67. func getConfig(sp interfaces.ServiceProvider) fiber.Config {
  68. return fiber.Config{
  69. AppName: appName,
  70. DisableStartupMessage: true,
  71. Views: getViewsEngine(sp.GetEnvService()),
  72. ErrorHandler: getErrorHandler(sp),
  73. }
  74. }
  75. // nolint
  76. func getJWTConfig(sp interfaces.ServiceProvider) jwt.Config {
  77. return jwt.Config{
  78. SigningKey: []byte(sp.GetEnvService().GetJWTSecretKey()),
  79. TokenLookup: "cookie:" + sp.GetEnvService().GetJWTCookie(),
  80. ErrorHandler: func(fctx *fiber.Ctx, err error) error {
  81. return fctx.Redirect("/admin/login")
  82. },
  83. Filter: func(fctx *fiber.Ctx) bool {
  84. method := fctx.Method()
  85. path := fctx.Path()
  86. if method != fiber.MethodGet && method != fiber.MethodPost &&
  87. method != fiber.MethodPut && method != fiber.MethodDelete {
  88. return true
  89. }
  90. if path == "/admin/login" {
  91. return true
  92. }
  93. return false
  94. },
  95. }
  96. }
  97. func getMetricsConfig() monitor.Config {
  98. return monitor.Config{
  99. Title: "dmitriygnatenko.ru metrics",
  100. }
  101. }
  102. func getCORSConfig(sp interfaces.ServiceProvider) cors.Config {
  103. return cors.Config{
  104. AllowOrigins: sp.GetEnvService().GetCORSAllowOrigins(),
  105. AllowMethods: sp.GetEnvService().GetCORSAllowMethods(),
  106. }
  107. }
  108. func getViewsEngine(env interfaces.Env) *html.Engine {
  109. engine := html.New(templatesPath, ".html")
  110. // nolint:gocritic
  111. engine.AddFunc("now", func() time.Time {
  112. return time.Now()
  113. })
  114. // nolint:gosec
  115. engine.AddFunc("noescape", func(str string) template.HTML {
  116. return template.HTML(str)
  117. })
  118. engine.AddFunc("gridsep", func(i, l int) bool {
  119. i++
  120. return i%3 == 0 && i != l
  121. })
  122. engine.AddFunc("version", func() string {
  123. return strconv.Itoa(env.GetStaticVersion())
  124. })
  125. engine.AddFunc("ga", func() string {
  126. return env.GetGAKey()
  127. })
  128. return engine
  129. }
  130. func getErrorHandler(sp interfaces.ServiceProvider) fiber.ErrorHandler {
  131. return func(fctx *fiber.Ctx, err error) error {
  132. errCode := fiber.StatusInternalServerError
  133. if e, ok := err.(*fiber.Error); ok {
  134. errCode = e.Code
  135. }
  136. if err.Error() != "" {
  137. errorsEmail := sp.GetEnvService().GetErrorsEmail()
  138. if errCode == fiber.StatusInternalServerError && errorsEmail != "" {
  139. log.Println(err)
  140. // nolint
  141. sp.GetMailerService().Send(
  142. errorsEmail,
  143. "AUTO - dmitriygnatenko.ru error",
  144. err.Error(),
  145. )
  146. }
  147. }
  148. var renderData fiber.Map
  149. if errCode == fiber.StatusNotFound {
  150. renderData = fiber.Map{
  151. "pageTitle": "Страница не найдена",
  152. "code": fiber.StatusNotFound,
  153. "text": "Запрашиваемая вами страница не найдена",
  154. }
  155. } else {
  156. renderData = fiber.Map{
  157. "pageTitle": "Внутренняя ошибка",
  158. "code": fiber.StatusInternalServerError,
  159. "text": "Внутренняя ошибка сервера, идем исправлять...",
  160. }
  161. }
  162. renderData["headTitle"] = "От слона к суслику - статьи про PHP, Go, алгоритмы"
  163. return fctx.Render("error", renderData, "_layout")
  164. }
  165. }