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")
}
}