fiber.go 6.7 KB

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