fiber.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. package fiber
  2. import (
  3. "html/template"
  4. "log"
  5. "strconv"
  6. "time"
  7. "github.com/gofiber/fiber/v2"
  8. "github.com/gofiber/fiber/v2/middleware/basicauth"
  9. "github.com/gofiber/fiber/v2/middleware/cors"
  10. "github.com/gofiber/fiber/v2/middleware/limiter"
  11. "github.com/gofiber/fiber/v2/middleware/monitor"
  12. "github.com/gofiber/fiber/v2/middleware/recover"
  13. jwt "github.com/gofiber/jwt/v3"
  14. "github.com/gofiber/template/html"
  15. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/repositories"
  16. authService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/auth"
  17. cacheService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/cache"
  18. envService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/env"
  19. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler"
  20. adminHandler "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler/admin"
  21. mailService "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/mailer"
  22. )
  23. const (
  24. appName = "dmitriygnatenko"
  25. templatesPath = "./../../internal/templates"
  26. staticPath = "../../web"
  27. metricsURI = "/metrics"
  28. loginRateLimiterMaxRequests = 10
  29. loginRateLimiterExpiration = 30 * time.Second
  30. )
  31. type (
  32. ServiceProvider interface {
  33. EnvService() *envService.Service
  34. MailerService() *mailService.Service
  35. AuthService() *authService.Service
  36. CacheService() *cacheService.Service
  37. ArticleRepository() *repositories.ArticleRepository
  38. TagRepository() *repositories.TagRepository
  39. ArticleTagRepository() *repositories.ArticleTagRepository
  40. UserRepository() *repositories.UserRepository
  41. }
  42. EnvService interface {
  43. StaticVersion() int
  44. GAKey() string
  45. }
  46. )
  47. func Init(sp ServiceProvider) (*fiber.App, error) {
  48. fiberApp := fiber.New(getConfig(sp))
  49. // Configure web root
  50. fiberApp.Static("/", staticPath)
  51. // Configure CORS middleware
  52. fiberApp.Use(cors.New(getCORSConfig(sp)))
  53. // Configure recover middleware
  54. fiberApp.Use(recover.New())
  55. // Configure JWT auth
  56. jwtAuth := jwt.New(getJWTConfig(sp))
  57. // Configure Basic auth
  58. basicAuth := basicauth.New(basicauth.Config{
  59. Users: map[string]string{
  60. sp.EnvService().BasicAuthUser(): sp.EnvService().BasicAuthPassword(),
  61. },
  62. })
  63. // Metrics
  64. fiberApp.Get(metricsURI, basicAuth, monitor.New(getMetricsConfig()))
  65. // Public handlers
  66. fiberApp.Get(
  67. "/",
  68. handler.MainPageHandler(
  69. sp.CacheService(),
  70. sp.ArticleRepository(),
  71. ),
  72. )
  73. fiberApp.Get(
  74. "/tag/:tag", handler.TagHandler(
  75. sp.CacheService(),
  76. sp.ArticleRepository(),
  77. sp.TagRepository(),
  78. ),
  79. )
  80. fiberApp.Get(
  81. "/article/:article",
  82. handler.ArticleHandler(
  83. sp.CacheService(),
  84. sp.ArticleRepository(),
  85. sp.TagRepository(),
  86. ),
  87. )
  88. // Protected handlers
  89. admin := fiberApp.Group("/admin", jwtAuth)
  90. admin.Get(
  91. "/",
  92. adminHandler.ArticleHandler(sp.ArticleRepository()),
  93. )
  94. admin.All("/login", limiter.New(limiter.Config{
  95. Max: loginRateLimiterMaxRequests,
  96. Expiration: loginRateLimiterExpiration,
  97. }), adminHandler.LoginHandler(
  98. sp.EnvService(),
  99. sp.AuthService(),
  100. sp.UserRepository(),
  101. ))
  102. admin.All(
  103. "/logout",
  104. adminHandler.LogoutHandler(sp.EnvService()),
  105. )
  106. admin.All(
  107. "/user/change-password",
  108. adminHandler.ChangePassword(
  109. sp.AuthService(),
  110. sp.UserRepository(),
  111. ),
  112. )
  113. admin.All(
  114. "/article/add",
  115. adminHandler.AddArticleHandler(
  116. sp.ArticleRepository(),
  117. sp.TagRepository(),
  118. sp.ArticleTagRepository(),
  119. sp.CacheService(),
  120. ),
  121. )
  122. admin.All(
  123. "/article/edit/:id<int>",
  124. adminHandler.EditArticleHandler(
  125. sp.ArticleRepository(),
  126. sp.TagRepository(),
  127. sp.ArticleTagRepository(),
  128. sp.CacheService(),
  129. ),
  130. )
  131. admin.All(
  132. "/article/delete/:id<int>",
  133. adminHandler.DeleteArticleHandler(
  134. sp.ArticleRepository(),
  135. sp.ArticleTagRepository(),
  136. sp.CacheService(),
  137. ),
  138. )
  139. admin.Get(
  140. "/tag",
  141. adminHandler.TagHandler(sp.TagRepository()),
  142. )
  143. admin.All(
  144. "/tag/add",
  145. adminHandler.AddTagHandler(
  146. sp.TagRepository(),
  147. sp.CacheService(),
  148. ),
  149. )
  150. admin.All(
  151. "/tag/edit/:id<int>",
  152. adminHandler.EditTagHandler(
  153. sp.TagRepository(),
  154. sp.CacheService(),
  155. ),
  156. )
  157. admin.All(
  158. "/tag/delete/:id<int>",
  159. adminHandler.DeleteTagHandler(
  160. sp.TagRepository(),
  161. sp.CacheService(),
  162. ),
  163. )
  164. return fiberApp, nil
  165. }
  166. func getConfig(sp ServiceProvider) fiber.Config {
  167. return fiber.Config{
  168. AppName: appName,
  169. DisableStartupMessage: true,
  170. Views: getViewsEngine(sp.EnvService()),
  171. ErrorHandler: getErrorHandler(sp),
  172. }
  173. }
  174. // nolint
  175. func getJWTConfig(sp ServiceProvider) jwt.Config {
  176. return jwt.Config{
  177. SigningKey: []byte(sp.EnvService().JWTSecretKey()),
  178. TokenLookup: "cookie:" + sp.EnvService().JWTCookie(),
  179. ErrorHandler: func(fctx *fiber.Ctx, err error) error {
  180. return fctx.Redirect("/admin/login")
  181. },
  182. Filter: func(fctx *fiber.Ctx) bool {
  183. method := fctx.Method()
  184. path := fctx.Path()
  185. if method != fiber.MethodGet && method != fiber.MethodPost &&
  186. method != fiber.MethodPut && method != fiber.MethodDelete {
  187. return true
  188. }
  189. if path == "/admin/login" {
  190. return true
  191. }
  192. return false
  193. },
  194. }
  195. }
  196. func getMetricsConfig() monitor.Config {
  197. return monitor.Config{
  198. Title: "dmitriygnatenko.ru metrics",
  199. }
  200. }
  201. func getCORSConfig(sp ServiceProvider) cors.Config {
  202. return cors.Config{
  203. AllowOrigins: sp.EnvService().CORSAllowOrigins(),
  204. AllowMethods: sp.EnvService().CORSAllowMethods(),
  205. }
  206. }
  207. func getViewsEngine(env EnvService) *html.Engine {
  208. engine := html.New(templatesPath, ".html")
  209. // nolint:gocritic
  210. engine.AddFunc("now", func() time.Time {
  211. return time.Now()
  212. })
  213. // nolint:gosec
  214. engine.AddFunc("noescape", func(str string) template.HTML {
  215. return template.HTML(str)
  216. })
  217. engine.AddFunc("gridsep", func(i, l int) bool {
  218. i++
  219. return i%3 == 0 && i != l
  220. })
  221. engine.AddFunc("version", func() string {
  222. return strconv.Itoa(env.StaticVersion())
  223. })
  224. engine.AddFunc("ga", func() string {
  225. return env.GAKey()
  226. })
  227. return engine
  228. }
  229. func getErrorHandler(sp ServiceProvider) fiber.ErrorHandler {
  230. return func(fctx *fiber.Ctx, err error) error {
  231. errCode := fiber.StatusInternalServerError
  232. if e, ok := err.(*fiber.Error); ok {
  233. errCode = e.Code
  234. }
  235. if err.Error() != "" {
  236. errorsEmail := sp.EnvService().ErrorsEmail()
  237. if errCode == fiber.StatusInternalServerError && errorsEmail != "" {
  238. log.Println(err)
  239. // nolint
  240. sp.MailerService().Send(
  241. errorsEmail,
  242. "AUTO - dmitriygnatenko.ru error",
  243. err.Error(),
  244. )
  245. }
  246. }
  247. var renderData fiber.Map
  248. if errCode == fiber.StatusNotFound {
  249. renderData = fiber.Map{
  250. "pageTitle": "Страница не найдена",
  251. "code": fiber.StatusNotFound,
  252. "text": "Запрашиваемая вами страница не найдена",
  253. }
  254. } else {
  255. renderData = fiber.Map{
  256. "pageTitle": "Внутренняя ошибка",
  257. "code": fiber.StatusInternalServerError,
  258. "text": "Внутренняя ошибка сервера, идем исправлять...",
  259. }
  260. }
  261. renderData["headTitle"] = "От слона к суслику - статьи про PHP, Go, алгоритмы"
  262. return fctx.Render("error", renderData, "_layout")
  263. }
  264. }