package server
import (
"context"
"html/template"
"os"
"path"
"strings"
"time"
"github.com/allegro/bigcache/v3"
"github.com/dustin/go-humanize"
"github.com/iris-contrib/middleware/cors"
"github.com/kataras/iris/v12"
"github.com/rs/zerolog/log"
"pluja.dev/kycnot.me/config"
"pluja.dev/kycnot.me/utils/pow"
)
type Server struct {
ListenAddr string
Router *iris.Application
PowChallenger *pow.PowChallenger
Cache *bigcache.BigCache
}
func NewServer(listenAddr string) *Server {
app := iris.New()
cache, err := bigcache.New(context.Background(), bigcache.Config{
Shards: 1024,
LifeWindow: 30 * (24 * time.Hour),
MaxEntriesInWindow: 1000 * 10 * 60,
MaxEntrySize: 500,
Verbose: false,
HardMaxCacheSize: 0,
})
if err != nil {
log.Fatal().Err(err).Msg("Could not initialize cache")
}
return &Server{
ListenAddr: listenAddr,
Router: app,
PowChallenger: &pow.PowChallenger{},
Cache: cache,
}
}
func (s *Server) Run() error {
s.SetupMiddleware()
s.RegisterRoutes()
s.RegisterViews()
s.PowChallenger.Init()
return s.Router.Listen(s.ListenAddr)
}
func (s *Server) SetupMiddleware() {
s.Router.Use(iris.Compression)
if config.Conf.Dev {
log.Debug().Msg("CORS is enabled")
crs := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
AllowCredentials: true,
})
s.Router.Use(crs)
} else {
crs := cors.New(cors.Options{
AllowedOrigins: []string{"https://kycnot.me*", "https://www.kycnot.me*"}, // allows everything, use that to change the hosts.
AllowCredentials: true,
})
s.Router.Use(crs)
}
if config.Conf.Dev {
s.Router.Logger().SetLevel("debug")
s.Router.Use(iris.Cache304(10 * time.Second))
} else {
s.Router.Logger().SetLevel("warn")
s.Router.Use(iris.Cache304(24 * 60 * 60))
}
}
func (s *Server) RegisterRoutes() {
// Static routes
s.Router.Favicon(path.Join(os.Getenv("ROOT_DIR"), "/frontend/static", "/assets/favicon.webp"))
s.Router.HandleDir("/static", iris.Dir(path.Join(os.Getenv("ROOT_DIR"), "/frontend/static")))
// GET
s.Router.Get("/", s.handleIndex)
s.Router.Get("/about", s.handleAbout)
s.Router.Get("/pending", s.handlePending)
s.Router.Get("/service/{name:string}", s.handleService)
s.Router.Get("/exchange/{name:string}", s.handleService)
s.Router.Get("/point/{id:string}", s.handleAttribute)
s.Router.Get("/attr/{id:string}", s.handleAttribute)
s.Router.Get("/request/service", s.handleRequestServiceForm)
// POST
s.Router.Post("/new/service", s.handlePostRequestServiceForm)
// API routes
api_v1 := s.Router.Party("/api/v1")
api_v1.Get("/pow/verify/{id}/{nonce:int}", s.handleVerifyPow)
api_v1.Get("/picture/{id}", s.handleApiPicture)
}
func (s *Server) RegisterViews() {
// Use blocks as the templating engine
blocks := iris.Blocks("./frontend/templates", ".gohtml")
if config.Conf.Dev {
blocks.Reload(true)
}
blocks.Engine.Funcs(template.FuncMap{
"attr": func(s string) template.HTMLAttr {
return template.HTMLAttr(s)
},
"safe": func(s string) template.HTML {
return template.HTML(s)
},
"shortText": func(s string) string {
if len(s) > 50 {
return strings.TrimSpace(s[:50]) + "..."
}
return s
},
"humanizePbTimeString": func(t string) string {
if t == "" {
return "Unknown"
}
layout := "2006-01-02 15:04:05.000Z"
tm, err := time.Parse(layout, t)
if err != nil {
return t
}
return humanize.Time(tm)
},
"dateString": func(t string) string {
if t == "" {
return "Unknown"
}
layout := "2006-01-02 15:04:05.000Z"
tm, err := time.Parse(layout, t)
if err != nil {
return t
}
return tm.Format("2006-01-02")
},
"isNew": func(t string) bool {
if t == "" {
return false
}
layout := "2006-01-02 15:04:05.000Z"
tm, err := time.Parse(layout, t)
if err != nil {
return false
}
return time.Since(tm) < 7*(24*time.Hour)
},
})
s.Router.RegisterView(blocks)
}