package server import ( "context" "html/template" "math/rand" "net/http" "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/kataras/iris/v12/middleware/rate" "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 HttpClient *http.Client } 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, HttpClient: &http.Client{}, } } func (s *Server) Run() error { s.SetupMiddleware() s.RegisterRoutes() s.RegisterViews() s.PowChallenger.Init() return s.Router.Listen(s.ListenAddr, iris.WithSitemap("https://kycnot.me")) } 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{"*"}, // 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) // Miscellaneous Routes s.Router.Get("/pgp", s.handlePgp) s.Router.Get("/nostr", s.handleNostr) // POST s.Router.Post("/new/service", s.handlePostRequestServiceForm) // API routes genericApi := s.Router.Party("/api/g") { genericApi.Get("/pow/verify/{id}/{nonce:int}", s.handleVerifyPow) genericApi.Get("/picture/{id}", s.handleApiPicture) } v1Api := s.Router.Party("/api/v1") { limitV1 := rate.Limit(rate.Every(1*time.Minute), 5, rate.PurgeEvery(time.Minute, 5*time.Minute)) v1Api.Use(limitV1) v1Api.Get("/service/{name:string}", s.handleApiService) v1Api.Get("/service/{name:string}/summary", s.handleScoreSummary) } } func (s *Server) RegisterViews() { // Use blocks as the templating engine blocks := iris.Blocks("./frontend/templates", ".html") 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) }, "randomElem": func(vs []string) string { if len(vs) == 0 { return "" } return vs[rand.Intn(len(vs))] }, "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) }