From f998ce61c6036ed3f8f31f0aee90eed12a4bf1fc Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Thu, 9 Jan 2025 02:41:03 +0100 Subject: [PATCH] Decoupling server / engine / kgpz --- app/kgpz.go | 71 +++++++++++++++ controllers/agent_controller.go | 6 +- controllers/category_controller.go | 4 +- controllers/issue_controller.go | 6 +- controllers/place_controller.go | 4 +- controllers/year_contoller.go | 6 +- server/statics.go => helpers/serverhelpers.go | 4 +- kgpz_web.go | 43 ++++++--- server/interfaces.go | 11 +++ server/server.go | 90 ++++++++----------- templating/engine.go | 38 ++++---- 11 files changed, 189 insertions(+), 94 deletions(-) rename server/statics.go => helpers/serverhelpers.go (80%) create mode 100644 server/interfaces.go diff --git a/app/kgpz.go b/app/kgpz.go index cb76b38..f3c813a 100644 --- a/app/kgpz.go +++ b/app/kgpz.go @@ -4,12 +4,37 @@ import ( "os" "sync" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/controllers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/gnd" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" + "github.com/gofiber/fiber/v2" +) + +// INFO: this holds all the stuff specific to the KGPZ application +// It implements Map(*fiber.App) error, so it can be used as a MuxProvider +// It also implements Funcs() map[string]interface{} to map funcs to a template engine + +const ( + ASSETS_URL_PREFIX = "/assets" + + EDITION_URL = "/edition/" + PRIVACY_URL = "/datenschutz/" + CONTACT_URL = "/kontakt/" + CITATION_URL = "/zitation/" + + INDEX_URL = "/1764" + + YEAR_OVERVIEW_URL = "/:year" + PLACE_OVERVIEW_URL = "/ort/:place" + AGENTS_OVERVIEW_URL = "/akteure/:letterorid" + CATEGORY_OVERVIEW_URL = "/kategorie/:category" + + ISSSUE_URL = "/:year/:issue/:page?" + ADDITIONS_URL = "/:year/:issue/beilage/:page?" ) type KGPZ struct { @@ -62,6 +87,52 @@ func (k *KGPZ) InitGND() { } } +func (k *KGPZ) Routes(srv *fiber.App) error { + srv.Get("/", func(c *fiber.Ctx) error { + c.Redirect(INDEX_URL) + return nil + }) + + srv.Get(PLACE_OVERVIEW_URL, controllers.GetPlace(k.Library)) + srv.Get(CATEGORY_OVERVIEW_URL, controllers.GetCategory(k.Library)) + srv.Get(AGENTS_OVERVIEW_URL, controllers.GetAgents(k.Library)) + + // TODO: YEAR_OVERVIEW_URL being /:year is a bad idea, since it captures basically everything, + // probably creating problems with static files, and also in case we add a front page later. + // That's why we redirect to /1764 on "/ " above and don´t use an optional /:year? paramter. + // -> Check SEO requirements on index pages that are 301 forwarded. + // This applies to all paths with two or three segments without a static prefix: + // Prob better to do /ausgabe/:year/:issue/:page? and /jahrgang/:year? respectively. + srv.Get(YEAR_OVERVIEW_URL, controllers.GetYear(k.Library)) + srv.Get(ISSSUE_URL, controllers.GetIssue(k.Library)) + srv.Get(ADDITIONS_URL, controllers.GetIssue(k.Library)) + + srv.Get(EDITION_URL, controllers.Get(EDITION_URL)) + srv.Get(PRIVACY_URL, controllers.Get(PRIVACY_URL)) + srv.Get(CONTACT_URL, controllers.Get(CONTACT_URL)) + srv.Get(CITATION_URL, controllers.Get(CITATION_URL)) + + return nil +} + +func (k *KGPZ) Funcs() map[string]interface{} { + e := make(map[string]interface{}) + // App specific + e["GetAgent"] = k.Library.Agents.Item + e["GetPlace"] = k.Library.Places.Item + e["GetWork"] = k.Library.Works.Item + e["GetCategory"] = k.Library.Categories.Item + e["GetIssue"] = k.Library.Issues.Item + e["GetPiece"] = k.Library.Pieces.Item + e["GetGND"] = k.GND.Person + + e["LookupPieces"] = k.Library.Pieces.ReverseLookup + e["LookupWorks"] = k.Library.Works.ReverseLookup + e["LookupIssues"] = k.Library.Issues.ReverseLookup + + return e +} + func (k *KGPZ) Enrich() error { if k.GND == nil { k.InitGND() diff --git a/controllers/agent_controller.go b/controllers/agent_controller.go index 7c7788a..5af68f7 100644 --- a/controllers/agent_controller.go +++ b/controllers/agent_controller.go @@ -3,9 +3,9 @@ package controllers import ( "strings" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/viewmodels" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" "github.com/gofiber/fiber/v2" ) @@ -13,11 +13,11 @@ const ( DEFAULT_AGENT = "a" ) -func GetAgents(kgpz *app.KGPZ) fiber.Handler { +func GetAgents(kgpz *xmlmodels.Library) fiber.Handler { return func(c *fiber.Ctx) error { a := c.Params("letterorid", DEFAULT_AGENT) a = strings.ToLower(a) - agents := viewmodels.AgentsView(a, kgpz.Library) + agents := viewmodels.AgentsView(a, kgpz) if len(agents.Agents) == 0 { logging.Error(nil, "No agents found for letter or id: "+a) return c.SendStatus(fiber.StatusNotFound) diff --git a/controllers/category_controller.go b/controllers/category_controller.go index e60f7ed..d7e32e3 100644 --- a/controllers/category_controller.go +++ b/controllers/category_controller.go @@ -1,11 +1,11 @@ package controllers import ( - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" "github.com/gofiber/fiber/v2" ) -func GetCategory(kgpz *app.KGPZ) fiber.Handler { +func GetCategory(kgpz *xmlmodels.Library) fiber.Handler { return func(c *fiber.Ctx) error { return c.Render("/kategorie/", nil) } diff --git a/controllers/issue_controller.go b/controllers/issue_controller.go index 012b6c3..ec2cd69 100644 --- a/controllers/issue_controller.go +++ b/controllers/issue_controller.go @@ -3,9 +3,9 @@ package controllers import ( "strconv" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/viewmodels" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" "github.com/gofiber/fiber/v2" ) @@ -14,7 +14,7 @@ const ( MAXYEAR = 1779 ) -func GetIssue(kgpz *app.KGPZ) fiber.Handler { +func GetIssue(kgpz *xmlmodels.Library) fiber.Handler { return func(c *fiber.Ctx) error { y := c.Params("year") yi, err := strconv.Atoi(y) @@ -30,7 +30,7 @@ func GetIssue(kgpz *app.KGPZ) fiber.Handler { return c.SendStatus(fiber.StatusNotFound) } - issue, err := viewmodels.NewSingleIssueView(y, d, kgpz.Library) + issue, err := viewmodels.NewSingleIssueView(y, d, kgpz) if err != nil { logging.Error(err, "Issue could not be found") diff --git a/controllers/place_controller.go b/controllers/place_controller.go index efcf20d..b322f29 100644 --- a/controllers/place_controller.go +++ b/controllers/place_controller.go @@ -1,11 +1,11 @@ package controllers import ( - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" "github.com/gofiber/fiber/v2" ) -func GetPlace(kgpz *app.KGPZ) fiber.Handler { +func GetPlace(kgpz *xmlmodels.Library) fiber.Handler { return func(c *fiber.Ctx) error { return c.Render("/ort/", nil) } diff --git a/controllers/year_contoller.go b/controllers/year_contoller.go index 88c9f66..47c6a00 100644 --- a/controllers/year_contoller.go +++ b/controllers/year_contoller.go @@ -3,13 +3,13 @@ package controllers import ( "strconv" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/viewmodels" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels" "github.com/gofiber/fiber/v2" ) -func GetYear(kgpz *app.KGPZ) fiber.Handler { +func GetYear(kgpz *xmlmodels.Library) fiber.Handler { return func(c *fiber.Ctx) error { y := c.Params("year", strconv.Itoa(MINYEAR)) yi, err := strconv.Atoi(y) @@ -19,7 +19,7 @@ func GetYear(kgpz *app.KGPZ) fiber.Handler { return c.SendStatus(fiber.StatusNotFound) } - view, err := viewmodels.YearView(yi, kgpz.Library) + view, err := viewmodels.YearView(yi, kgpz) if err != nil { logging.ErrorDebug(err, "Keine Ausgaben für das Jahr "+y) return c.SendStatus(fiber.StatusNotFound) diff --git a/server/statics.go b/helpers/serverhelpers.go similarity index 80% rename from server/statics.go rename to helpers/serverhelpers.go index 6d186f9..6590fb1 100644 --- a/server/statics.go +++ b/helpers/serverhelpers.go @@ -1,4 +1,4 @@ -package server +package helpers import ( "io/fs" @@ -8,7 +8,7 @@ import ( "github.com/gofiber/fiber/v2/middleware/filesystem" ) -func static(fs *fs.FS) fiber.Handler { +func StaticHandler(fs *fs.FS) fiber.Handler { return filesystem.New(filesystem.Config{ Root: http.FS(*fs), Browse: false, diff --git a/kgpz_web.go b/kgpz_web.go index b457a56..bc5e617 100644 --- a/kgpz_web.go +++ b/kgpz_web.go @@ -21,12 +21,26 @@ const ( DEV_CONFIG = "config.dev.json" ) +type App struct { + KGPZ *app.KGPZ + Server *server.Server + Config *providers.ConfigProvider + Engine *templating.Engine +} + func main() { cfg := providers.NewConfigProvider([]string{DEV_CONFIG, DEFAULT_CONFIG}) if err := cfg.Read(); err != nil { helpers.Assert(err, "Error reading config") } + app, err := Init(cfg) + helpers.Assert(err, "Error initializing app") + + Run(app) +} + +func Init(cfg *providers.ConfigProvider) (*App, error) { if cfg.Config.Debug { logging.SetDebug() } else { @@ -34,14 +48,20 @@ func main() { } kgpz := app.NewKGPZ(cfg) + // TODO: this must return an error on failure kgpz.Init() - server := server.Create(kgpz, cfg, Engine(kgpz, cfg)) - Start(kgpz, server, cfg) + engine := Engine(kgpz, cfg) + server := server.Create(cfg, engine) + + server.AddPre(engine) + server.AddMux(kgpz) + + return &App{KGPZ: kgpz, Server: server, Config: cfg, Engine: engine}, nil } -func Start(k *app.KGPZ, s *server.Server, c *providers.ConfigProvider) { - s.Start() +func Run(app *App) { + app.Server.Start() sigs := make(chan os.Signal, 1) done := make(chan bool, 1) @@ -53,19 +73,19 @@ func Start(k *app.KGPZ, s *server.Server, c *providers.ConfigProvider) { logging.Info("Signal received, Cleaning up...") // INFO: here we add cleanup functions if sig == syscall.SIGTERM { - s.Stop() + app.Server.Stop() logging.Info("Server stopped. Waiting for FS.") } else { - s.Kill() + app.Server.Kill() logging.Info("Server killed. Waiting for FS.") } - k.Shutdown() + app.KGPZ.Shutdown() logging.Info("Shutdown complete.") done <- true }() // INFO: hot reloading for poor people - if c.Watch { + if app.Config.Watch { go func() { _, routesexist := os.Stat(server.ROUTES_FILEPATH) _, layoutexist := os.Stat(server.LAYOUT_FILEPATH) @@ -83,7 +103,10 @@ func Start(k *app.KGPZ, s *server.Server, c *providers.ConfigProvider) { watcher.Append(func(path string) { logging.Info("File changed: ", path) time.Sleep(200 * time.Millisecond) - s.Engine(Engine(k, c)) + engine := Engine(app.KGPZ, app.Config) + app.Server.ClearPre() + app.Server.AddPre(engine) + app.Server.Engine(engine) }) if routesexist != nil { @@ -125,7 +148,7 @@ func Start(k *app.KGPZ, s *server.Server, c *providers.ConfigProvider) { func Engine(kgpz *app.KGPZ, c *providers.ConfigProvider) *templating.Engine { e := templating.NewEngine(&views.LayoutFS, &views.RoutesFS) - e.Funcs(kgpz) + e.AddFuncs(kgpz.Funcs()) e.Globals(fiber.Map{"isDev": c.Config.Debug, "name": "KGPZ", "lang": "de"}) return e } diff --git a/server/interfaces.go b/server/interfaces.go new file mode 100644 index 0000000..fe0365a --- /dev/null +++ b/server/interfaces.go @@ -0,0 +1,11 @@ +package server + +import "github.com/gofiber/fiber/v2" + +type MuxProvider interface { + Routes(app *fiber.App) error +} + +type PreMuxProvider interface { + Pre(app *fiber.App) error +} diff --git a/server/server.go b/server/server.go index 51144cb..5dec658 100644 --- a/server/server.go +++ b/server/server.go @@ -4,16 +4,12 @@ import ( "sync" "time" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/controllers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/templating" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/views" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" - "github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/storage/memory/v2" @@ -29,25 +25,6 @@ const ( CACHE_GC_INTERVAL = 120 * time.Second ) -const ( - ASSETS_URL_PREFIX = "/assets" - - EDITION_URL = "/edition/" - PRIVACY_URL = "/datenschutz/" - CONTACT_URL = "/kontakt/" - CITATION_URL = "/zitation/" - - INDEX_URL = "/1764" - - YEAR_OVERVIEW_URL = "/:year" - PLACE_OVERVIEW_URL = "/ort/:place" - AGENTS_OVERVIEW_URL = "/akteure/:letterorid" - CATEGORY_OVERVIEW_URL = "/kategorie/:category" - - ISSSUE_URL = "/:year/:issue/:page?" - ADDITIONS_URL = "/:year/:issue/beilage/:page?" -) - const ( STATIC_FILEPATH = "./views/assets" ROUTES_FILEPATH = "./views/routes" @@ -69,18 +46,20 @@ type Server struct { engine *templating.Engine mu sync.Mutex - kgpz *app.KGPZ + // Maybe that is to much, it should just be a list of method, path, handler structs + // in the order in which they are ought to be mapped. + muxproviders []MuxProvider + premuxproviders []PreMuxProvider } -func Create(k *app.KGPZ, c *providers.ConfigProvider, e *templating.Engine) *Server { - if c == nil || k == nil { +func Create(c *providers.ConfigProvider, e *templating.Engine) *Server { + if c == nil { logging.Error(nil, "Error creating server: Config or App is posssibly nil.") return nil } return &Server{ Config: c, - kgpz: k, engine: e, } } @@ -93,6 +72,22 @@ func (s *Server) Engine(e *templating.Engine) { s.Start() } +func (s *Server) AddMux(m MuxProvider) { + s.muxproviders = append(s.muxproviders, m) +} + +func (s *Server) ClearMux() { + s.muxproviders = []MuxProvider{} +} + +func (s *Server) AddPre(m PreMuxProvider) { + s.premuxproviders = append(s.premuxproviders, m) +} + +func (s *Server) ClearPre() { + s.premuxproviders = []PreMuxProvider{} +} + // TODO: There is no error handler func (s *Server) Start() { s.mu.Lock() @@ -130,15 +125,20 @@ func (s *Server) Start() { ViewsLayout: templating.DEFAULT_LAYOUT_NAME, }) + for _, m := range s.premuxproviders { + err := m.Pre(srv) + if err != nil { + logging.Error(err, "Error mapping premuxprovider") + return + } + } + if s.Config.Debug { srv.Use(logger.New()) } srv.Use(recover.New()) - srv.Use(ASSETS_URL_PREFIX, etag.New()) - srv.Use(ASSETS_URL_PREFIX, static(&views.StaticFS)) - // TODO: Dont cache static assets, bc storage gets huge on images. // -> Maybe fiber does this already, automatically? if s.Config.Debug { @@ -157,29 +157,13 @@ func (s *Server) Start() { })) } - srv.Get("/", func(c *fiber.Ctx) error { - c.Redirect(INDEX_URL) - return nil - }) - - srv.Get(PLACE_OVERVIEW_URL, controllers.GetPlace(s.kgpz)) - srv.Get(CATEGORY_OVERVIEW_URL, controllers.GetCategory(s.kgpz)) - srv.Get(AGENTS_OVERVIEW_URL, controllers.GetAgents(s.kgpz)) - - // TODO: YEAR_OVERVIEW_URL being /:year is a bad idea, since it captures basically everything, - // probably creating problems with static files, and also in case we add a front page later. - // That's why we redirect to /1764 on "/ " above and don´t use an optional /:year? paramter. - // -> Check SEO requirements on index pages that are 301 forwarded. - // This applies to all paths with two or three segments without a static prefix: - // Prob better to do /ausgabe/:year/:issue/:page? and /jahrgang/:year? respectively. - srv.Get(YEAR_OVERVIEW_URL, controllers.GetYear(s.kgpz)) - srv.Get(ISSSUE_URL, controllers.GetIssue(s.kgpz)) - srv.Get(ADDITIONS_URL, controllers.GetIssue(s.kgpz)) - - srv.Get(EDITION_URL, controllers.Get(EDITION_URL)) - srv.Get(PRIVACY_URL, controllers.Get(PRIVACY_URL)) - srv.Get(CONTACT_URL, controllers.Get(CONTACT_URL)) - srv.Get(CITATION_URL, controllers.Get(CITATION_URL)) + for _, m := range s.muxproviders { + err := m.Routes(srv) + if err != nil { + logging.Error(err, "Error mapping muxprovider") + return + } + } s.runner(srv) diff --git a/templating/engine.go b/templating/engine.go index 1fb3a55..b1bb8f9 100644 --- a/templating/engine.go +++ b/templating/engine.go @@ -7,10 +7,15 @@ import ( "strings" "sync" - "github.com/Theodor-Springmann-Stiftung/kgpz_web/app" "github.com/Theodor-Springmann-Stiftung/kgpz_web/functions" + "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/views" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/etag" +) + +const ( + ASSETS_URL_PREFIX = "/assets" ) type Engine struct { @@ -30,14 +35,14 @@ func NewEngine(layouts, templates *fs.FS) *Engine { mu: &sync.Mutex{}, LayoutRegistry: NewLayoutRegistry(*layouts), TemplateRegistry: NewTemplateRegistry(*templates), + FuncMap: make(template.FuncMap), } - + e.funcs() return &e } -func (e *Engine) Funcs(app *app.KGPZ) error { +func (e *Engine) funcs() error { e.mu.Lock() - e.FuncMap = make(map[string]interface{}) e.mu.Unlock() // Dates @@ -58,19 +63,12 @@ func (e *Engine) Funcs(app *app.KGPZ) error { // Embedding of XSLT files e.AddFunc("EmbedXSLT", functions.EmbedXSLT(views.StaticFS)) - // App specific - e.AddFunc("GetAgent", app.Library.Agents.Item) - e.AddFunc("GetPlace", app.Library.Places.Item) - e.AddFunc("GetWork", app.Library.Works.Item) - e.AddFunc("GetCategory", app.Library.Categories.Item) - e.AddFunc("GetIssue", app.Library.Issues.Item) - e.AddFunc("GetPiece", app.Library.Pieces.Item) - e.AddFunc("GetGND", app.GND.Person) - - e.AddFunc("LookupPieces", app.Library.Pieces.ReverseLookup) - e.AddFunc("LookupWorks", app.Library.Works.ReverseLookup) - e.AddFunc("LookupIssues", app.Library.Issues.ReverseLookup) + return nil +} +func (e Engine) Pre(srv *fiber.App) error { + srv.Use(ASSETS_URL_PREFIX, etag.New()) + srv.Use(ASSETS_URL_PREFIX, helpers.StaticHandler(&views.StaticFS)) return nil } @@ -129,6 +127,14 @@ func (e *Engine) AddFunc(name string, fn interface{}) { e.FuncMap[name] = fn } +func (e *Engine) AddFuncs(funcs map[string]interface{}) { + e.mu.Lock() + defer e.mu.Unlock() + for k, v := range funcs { + e.FuncMap[k] = v + } +} + func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error { // TODO: check if a reload is needed if files on disk have changed ld := data.(fiber.Map)