diff --git a/go.mod b/go.mod index 988dda5..b8d4bc1 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module ruben/inventory2 go 1.24.2 -replace github.com/angelbeltran/templater v0.1.0 => ../../github.com/angelbeltran/templater +replace github.com/angelbeltran/templater v0.2.2 => ../../github.com/angelbeltran/templater require github.com/jackc/pgx/v5 v5.7.6 diff --git a/go.sum b/go.sum index 14a4ee2..310ac28 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4 github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c h1:BIa061RHaIVP9TjpIQf0PldVpQmeln/2U1Cf2w8ALNI= github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks= -github.com/angelbeltran/templater v0.2.1 h1:9N2eNRE5/sYUgu7nZrU6VSSrrpKcOTlAw+UeeK3kyhk= -github.com/angelbeltran/templater v0.2.1/go.mod h1:ZA0T/ZmXxPi3gFjgp+RwEkcY9Q+JGyMStGw0c1jvgqA= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= diff --git a/internal/server/api/templates/router.go b/internal/server/api/templates/router.go index 021a81a..c8c947e 100644 --- a/internal/server/api/templates/router.go +++ b/internal/server/api/templates/router.go @@ -1,14 +1,14 @@ package templates import ( + "encoding/json" "errors" "fmt" "net/http" "net/url" - "path" - "path/filepath" "strconv" "strings" + "text/template" "ruben/inventory2/internal/domains/accounts" etsy_platform "ruben/inventory2/internal/domains/platforms/etsy" @@ -43,23 +43,60 @@ func SetupRoutes( logger *logging.Logger, r gin.IRouter, contentDir string, - tmpl *templater.Templater, rawEvents *raw_events.Store, accts *accounts.Store, etsy *etsy_platform.Platform, authMiddleware *middleware.Auth, ) { + s := &webpageRouter{ - log: logger, - contentDir: contentDir, - templater: tmpl, + log: logger, + contentDir: contentDir, + // TODO: clean up the references to the templates dir throughout as well (should only be referred to in the 'main' package + templater: new(templater.Templater).With(templater.Config{ + Funcs: func(name string, props map[string]any) template.FuncMap { + return template.FuncMap{ + // parsing + "parseInt": func(s string) (int, error) { + return strconv.Atoi(s) + }, + "parseInt64": func(s string) (int64, error) { + return strconv.ParseInt(s, 10, 64) + }, + "parsePlatform": func(s string) (accounts.Platform, error) { + return accounts.NewPlatform(s) + }, + + // arithmetic + "addInt": func(a, b int) int { + return a + b + }, + "subInt": func(a, b int) int { + return a - b + }, + "multInt": func(a, b int) int { + return a * b + }, + + // json + "prettyPrintJSON": func(j json.RawMessage) string { + b, err := json.MarshalIndent(j, " ", "") + if err != nil { + return string(j) + } + return string(b) + }, + "marshalJSON": json.Marshal, + } + }, + }), rawEvents: rawEvents, accts: accts, etsy: etsy, authMiddleware: authMiddleware, } - fn2 := s.authMiddleware.AuthenticateAndAddIdentity(s.serveTemplates) + authAndServeTemplate := s.authMiddleware.AuthenticateAndAddIdentity(s.serveTemplate) r.GET("/*rest", response.Handler(func(c *gin.Context) (response.Response, error) { c.Request.URL.Path = c.Request.URL.Path[3:] @@ -73,48 +110,21 @@ func SetupRoutes( if err != nil { return nil, err } - return s.serveTemplates(c) + return s.serveTemplate(c) } - return fn2(c) + + return authAndServeTemplate(c) })) } // GET / // compiles the page template or component template matching the url -// func (s *Server) serveTemplates(r *http.Request) (response.Response, error) { -func (s *webpageRouter) serveTemplates(c *gin.Context) (response.Response, error) { +func (s *webpageRouter) serveTemplate(c *gin.Context) (response.Response, error) { r := c.Request - name, args := s.getTemplateNameAndArgs(r, s.contentDir+"/templates/components") - fmt.Println("name:", name) - fmt.Println("args:", args) - - // TODO: update - b, err := s.templater.ExecuteComponent(name, args...) - if err == nil { - return response.HTML(b), nil - } - - if !isFileNotFoundError(err) { - return s.handleTemplateError(err, args...) - } - - name, args = s.getTemplateNameAndArgs(r, s.contentDir+"/templates/pages") - fmt.Println("name (page):", name) - fmt.Println("args (page):", args) - - if b, err = s.templater.ExecutePage(name, args...); err == nil { - return response.HTML(b), nil - } - - return s.handleTemplateError(err, args...) -} - -// func (s *Server) getTemplateNameAndArgs(r *http.Request, templateDir string) (name string, args []any) { -func (s *webpageRouter) getTemplateNameAndArgs(r *http.Request, templateDir string) (name string, args []any) { ctx := r.Context() - name, pathParams := getTemplateNameForURL(r.URL, templateDir) - return name, []any{ + b, err := s.templater.Execute( + strings.Trim(r.URL.Path, "/"), "Request", r, // add services and data here @@ -122,95 +132,25 @@ func (s *webpageRouter) getTemplateNameAndArgs(r *http.Request, templateDir stri s.rawEvents.WithContext(ctx), "URLCalc", newURLCalculator(r.URL), - "PathParams", - pathParams, "Accounts", s.accts.WithContext(ctx), "Etsy", s.etsy.WithContext(ctx), - // TODO: apply auth to all templates needed! // auth tooling - /* - AccessToken string - Claims authentication.AccessTokenClaims - User accounts.OAuthUser - Account *accounts.Account - */ "Identity", - middleware.GetIdentity(r.Context()), + middleware.GetIdentity(ctx), "Auth", newTemplateAuthenticator(r), + ) + if err != nil { + return s.handleTemplateError(err) } + + return response.HTML(b), nil } -// TODO: clean this up... -// TODO: somehow tell what the path params are and pass them up. -// - then consider pushing this functionality into the template library. -// -// getComponentTemplateNameForURL eliminate any trailing .html or /, and checks for any -// file with path parameters in the name, eg '{abc}.html.tmpl', prefering exact filename matches. -func getTemplateNameForURL(u *url.URL, templateDir string) (name string, params map[string]string) { - templateDir = path.Clean(templateDir) - fp := strings.TrimPrefix(strings.TrimSuffix(strings.TrimSuffix(u.Path, ".html"), "/"), "/") - if fp == "" { - // "/" maps to "/index" - fp = "index" - } - - fpParts := strings.Split(fp, "/") - res := getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(fpParts) - for _, combs := range res { - pattern := path.Join(templateDir, path.Join(combs...)) + ".html.tmpl" - - matches, _ := filepath.Glob(pattern) - if len(matches) == 0 { - pattern := path.Join(templateDir, path.Join(combs...), "index") + ".html.tmpl" - matches, _ = filepath.Glob(pattern) - } - if len(matches) > 0 { - match := matches[0] - name = strings.TrimPrefix(strings.TrimSuffix(match, ".html.tmpl"), templateDir+"/") - - patternParts := strings.Split(name, "/") - params = make(map[string]string) - for i, pp := range patternParts { - if strings.HasPrefix(pp, "{") && strings.HasSuffix(pp, "}") { - params[pp[1:len(pp)-1]] = fpParts[i] - } - } - - return name, params - } - } - - return fp, nil -} - -func getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(filepathParts []string) [][]string { - switch len(filepathParts) { - case 0: - return nil - case 1: - return [][]string{ - []string{filepathParts[0]}, - []string{"{*}"}, - } - default: - tailCombs := getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(filepathParts[1:]) - - combs := make([][]string, 2*len(tailCombs)) - for i, c := range tailCombs { - combs[i*2] = append([]string{filepathParts[0]}, c...) - combs[i*2+1] = append([]string{"{*}"}, c...) - } - - return combs - } -} - -// func (s *Server) handleTemplateError(err error, templateArgs ...any) (response.Response, error) { -func (s *webpageRouter) handleTemplateError(err error, templateArgs ...any) (response.Response, error) { +func (s *webpageRouter) handleTemplateError(err error) (response.Response, error) { if isFileNotFoundError(err) { return nil, response.NotFound(). Wrap(ErrTemplateNotFound{ @@ -223,31 +163,10 @@ func (s *webpageRouter) handleTemplateError(err error, templateArgs ...any) (res } func isFileNotFoundError(err error) bool { - /* - var pe *os.PathError - isPathErr := errors.As(err, &pe) - - return isPathErr && pe.Err != nil && pe.Err.Error() == "no such file or directory" - */ - var te *templater.ErrNotTemplateFileFound return errors.As(err, &te) } -func getHTTPStatusCode(err error) int { - rerr, ok := response.GetError(err) - if !ok { - return http.StatusInternalServerError - } - - code, ok := rerr.GetStatus() - if !ok { - return http.StatusInternalServerError - } - - return code -} - // template tooling type URLCalculator struct { diff --git a/internal/server/server.go b/internal/server/server.go index 023da95..e241c7c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,15 +2,10 @@ package server import ( "context" - "encoding/json" - "fmt" - "html/template" "net/http" "path" - "strconv" "strings" - "github.com/angelbeltran/templater" "github.com/gin-gonic/gin" "ruben/inventory2/internal/domains/accounts" @@ -73,56 +68,6 @@ func Router( logger.WithGroup("templates"), r.Group("/ui"), contentDir, - // TODO: move this constructor call up to main.go - // TODO: clean up the references to the templates dir throughout as well (should only be referred to in the 'main' package - new(templater.Templater).With(templater.Config{ - Funcs: func(name string, props map[string]any) template.FuncMap { - return template.FuncMap{ - // params - "addPathParam": func(k string, v any, args map[string]any) (map[string]any, error) { - pathParams, ok := args["PathParams"].(map[string]any) - if !ok { - return nil, fmt.Errorf("PathParams not set as args: %v", args) - } - - pathParams[k] = fmt.Sprint(v) - - return args, nil - }, - - // parsing - "parseInt": func(s string) (int, error) { - return strconv.Atoi(s) - }, - "parseInt64": func(s string) (int64, error) { - return strconv.ParseInt(s, 10, 64) - }, - "parsePlatform": func(s string) (accounts.Platform, error) { - return accounts.NewPlatform(s) - }, - - // arithmetic - "addInt": func(a, b int) int { - return a + b - }, - "subInt": func(a, b int) int { - return a - b - }, - "multInt": func(a, b int) int { - return a * b - }, - - // json - "prettyPrintJSON": func(j json.RawMessage) string { - b, err := json.MarshalIndent(j, " ", "") - if err != nil { - return string(j) - } - return string(b) - }, - } - }, - }), rawEvents, accts, etsy, diff --git a/templates/components/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}.html.tmpl b/templates/components/accounts/{acctID.int64}/inventory/sync-groups/draft/listings/{orderIndex.int}.html.tmpl similarity index 87% rename from templates/components/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}.html.tmpl rename to templates/components/accounts/{acctID.int64}/inventory/sync-groups/draft/listings/{orderIndex.int}.html.tmpl index ba24106..bc11006 100644 --- a/templates/components/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}.html.tmpl +++ b/templates/components/accounts/{acctID.int64}/inventory/sync-groups/draft/listings/{orderIndex.int}.html.tmpl @@ -1,12 +1,8 @@ {{/* TODO: make the save button disabled based on an api call */}} -{{/* .Identity.Account.AccountID, .Accounts */}} -{{- $dot := or .dot . -}} - -{{- $acctID := $dot.Identity.Account.AccountID }} - -{{- $orderIndex := or $dot.OrderIndex (parseInt $dot.PathParams.orderIndex) -}} -{{- $entry := $dot.Accounts.GetSyncGroupListingDraft $acctID $orderIndex }} +{{- $acctID := .PathParams.acctID }} +{{- $orderIndex := .PathParams.orderIndex }} +{{- $entry := .Accounts.GetSyncGroupListingDraft $acctID $orderIndex }} {{- $selectedShopPlatform := $entry.Platform }} {{- $selectedShopID := $entry.ShopID }} {{- $selectedListingID := $entry.ListingID }} @@ -30,7 +26,7 @@ " > - {{- $shops := $dot.Accounts.GetShops $acctID -}} + {{- $shops := .Accounts.GetShops $acctID -}} @@ -58,11 +49,9 @@ send setDisabled(disabled: not allRowsFilledOut or numRows < 2) to #create-sync-group-button " > - {{- $listings := $dot.Accounts.GetSyncGroupListingDrafts $acctID }} + {{- $listings := .Accounts.GetSyncGroupListingDrafts $acctID }} {{- range $listing := $listings }} - {{- component "accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}" - "dot" ($dot | addPathParam "orderIndex" $listing.OrderIndex) - }} + {{- component (printf "accounts/%d/inventory/sync-groups/draft/listings/%d" $acctID $listing.OrderIndex) }} {{- end }} diff --git a/templates/pages/accounts/{acctID}/index.html.tmpl b/templates/pages/accounts/{acctID.int64}/index.html.tmpl similarity index 100% rename from templates/pages/accounts/{acctID}/index.html.tmpl rename to templates/pages/accounts/{acctID.int64}/index.html.tmpl diff --git a/templates/pages/accounts/{acctID}/inventory.html.tmpl b/templates/pages/accounts/{acctID.int64}/inventory.html.tmpl similarity index 88% rename from templates/pages/accounts/{acctID}/inventory.html.tmpl rename to templates/pages/accounts/{acctID.int64}/inventory.html.tmpl index 889cee5..532d599 100644 --- a/templates/pages/accounts/{acctID}/inventory.html.tmpl +++ b/templates/pages/accounts/{acctID.int64}/inventory.html.tmpl @@ -17,7 +17,8 @@

(Draft 2)

- {{ component "accounts/{acctID}/inventory/sync-groups/draft/table" "dot" . }} + {{/* component "accounts/{acctID}/inventory/sync-groups/draft/table" "dot" . */}} + {{ component (printf "accounts/%d/inventory/sync-groups/draft/table" $acctID) "dot" . }}
{{ component "button" diff --git a/templates/pages/accounts/{acctID}/reports.html.tmpl b/templates/pages/accounts/{acctID.int64}/reports.html.tmpl similarity index 100% rename from templates/pages/accounts/{acctID}/reports.html.tmpl rename to templates/pages/accounts/{acctID.int64}/reports.html.tmpl