package ersteller_lib import ( "maragu.dev/gomponents" htmx "maragu.dev/gomponents-htmx" "net/http" "strings" ) type HtmxContext interface { Render(node gomponents.Node) SetError(err error) GetLanguage() Language GetActivePath() *ActivePath HasActivePath() bool SetActivePath(activePath *ActivePath) GetPath() string GetPathParam(key string, defaultValue ...string) string } type HtmxContextImpl struct { req *http.Request res http.ResponseWriter language Language activePath *ActivePath } func (c *HtmxContextImpl) SetError(err error) { _, writeErr := c.res.Write([]byte(err.Error())) if writeErr != nil { Error("failed to write error", writeErr) } c.res.WriteHeader(500) } func (c *HtmxContextImpl) GetPathParam(key string, defaultValue ...string) string { pathParam := c.req.PathValue(key) if pathParam != "" { return pathParam } if len(defaultValue) > 0 { return defaultValue[0] } return "" } func (c *HtmxContextImpl) GetPath() string { return c.req.URL.Path } func (c *HtmxContextImpl) SetActivePath(activePath *ActivePath) { c.activePath = activePath } func (c *HtmxContextImpl) GetActivePath() *ActivePath { return c.activePath } func (c *HtmxContextImpl) HasActivePath() bool { return c.activePath != nil } func (c *HtmxContextImpl) GetLanguage() Language { return c.language } func NewHtmxContext(req *http.Request, res http.ResponseWriter, language Language) HtmxContext { return &HtmxContextImpl{req: req, res: res, language: language} } func (c *HtmxContextImpl) Render(node gomponents.Node) { err := node.Render(c.res) if err != nil { Error("failed to render", err) c.SetError(err) } } type HtmxRouteFunc func(ctx HtmxContext) type HtmxPathParam struct { Key string Value string } type HtmxRoute interface { WithPathParams(params ...HtmxPathParam) HtmxRoute ToUrl(language Language, queryParams ...HtmxPathParam) string GetHtmx(language Language, queryParams ...HtmxPathParam) gomponents.Node SetActivePath(activePath *ActivePath) HtmxRoute Add(server *http.ServeMux) Execute(c HtmxContext) } func addLanguageToPath(path string, language Language) string { return "/" + string(language) + path } type CommonHtmxRoute struct { Paths LanguagePaths RouteFunc HtmxRouteFunc PathParams []HtmxPathParam ActivePath *ActivePath Method string HtmxMethod HtmxHttpMethodFunction } func NewCommonHtmxRoute(routeFunc HtmxRouteFunc, paths LanguagePaths, method string, htmxMethod HtmxHttpMethodFunction) *CommonHtmxRoute { return &CommonHtmxRoute{RouteFunc: routeFunc, Paths: paths, Method: method, HtmxMethod: htmxMethod} } func (h CommonHtmxRoute) Execute(c HtmxContext) { h.RouteFunc(c) } func (h CommonHtmxRoute) SetActivePath(activePath *ActivePath) HtmxRoute { h.ActivePath = activePath return h } func (h CommonHtmxRoute) AddWithMethod(method string, server *http.ServeMux) { for language, path := range h.Paths { server.HandleFunc(method+" "+addLanguageToPath(path, language), func(res http.ResponseWriter, req *http.Request) { context := NewHtmxContext(req, res, language) if h.ActivePath != nil { context.SetActivePath(h.ActivePath) } h.RouteFunc(context) }) } } func (h CommonHtmxRoute) Add(server *http.ServeMux) { h.AddWithMethod(h.Method, server) } func (h CommonHtmxRoute) WithPathParams(params ...HtmxPathParam) HtmxRoute { h.PathParams = params return h } func (h CommonHtmxRoute) ToUrl(language Language, queryParams ...HtmxPathParam) string { path := addLanguageToPath(h.Paths[language], language) if len(h.PathParams) == 0 { return path } for _, param := range h.PathParams { path = strings.ReplaceAll(path, "{"+param.Key+"}", param.Value) } return path } type HtmxHttpMethodFunction func(url string) gomponents.Node func (h CommonHtmxRoute) GetHtmxFromMethod(method HtmxHttpMethodFunction, language Language, queryParams ...HtmxPathParam) gomponents.Node { return method(h.ToUrl(language, queryParams...)) } func (h CommonHtmxRoute) GetHtmx(language Language, queryParams ...HtmxPathParam) gomponents.Node { return h.GetHtmxFromMethod(h.HtmxMethod, language, queryParams...) } func NewHtmxGetRoute(routeFunc HtmxRouteFunc, paths LanguagePaths) HtmxRoute { return NewCommonHtmxRoute(routeFunc, paths, "GET", htmx.Get) } func NewHtmxPostRoute(routeFunc HtmxRouteFunc, paths LanguagePaths) HtmxRoute { return NewCommonHtmxRoute(routeFunc, paths, "POST", htmx.Post) } func HandleStaticAndDefaultPath(server *http.ServeMux, defaultRoute HtmxRoute, defaultLanguage Language) func(http.ResponseWriter, *http.Request) { staticHandler := http.StripPrefix("/static", http.FileServer(http.Dir("./static"))) server.Handle("GET /static", staticHandler) return func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if strings.HasPrefix(path, "/static") { staticHandler.ServeHTTP(w, r) return } if path == "/" { defaultRoute.Execute(NewHtmxContext(r, w, defaultLanguage)) return } http.Error(w, "Not found", http.StatusNotFound) } }