package server import ( "context" "encoding/json" "errors" "fmt" "html/template" "log/slog" "net/http" "path" "strconv" "github.com/angelbeltran/templater" "ruben/inventory2/internal/domains/accounts" "ruben/inventory2/internal/domains/authentication" etsy_platform "ruben/inventory2/internal/domains/platforms/etsy" "ruben/inventory2/internal/domains/raw_events" accounts_api "ruben/inventory2/internal/server/api/accounts" auth_api "ruben/inventory2/internal/server/api/auth" templates_api "ruben/inventory2/internal/server/api/templates" "ruben/inventory2/internal/server/middleware" "ruben/inventory2/internal/server/response" "ruben/inventory2/internal/server/router" "ruben/inventory2/internal/server/webhooks" etsy_webhooks "ruben/inventory2/internal/server/webhooks/etsy" ) type Server struct { log *slog.Logger *router.Mux contentDir string templater *templater.Templater rawEvents *raw_events.Store accts *accounts.Store etsy *etsy_platform.Platform auth *authentication.Authenticator authMiddleware *middleware.Auth } func NewServer( ctx context.Context, logger *slog.Logger, contentDir string, rawEvents *raw_events.Store, accts *accounts.Store, etsy *etsy_platform.Platform, auth *authentication.Authenticator, ) *Server { mux := router.NewMux( logger, middleware.LogRequests(ctx, logger.WithGroup("request")), ) tmpl := templater.NewTemplater( contentDir+"/templates", func() 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]string) if !ok { return nil, fmt.Errorf("PathParams no set are 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) }, } }, ) s := &Server{ // TODO: eliminate fields that aren't needed anymore log: logger, Mux: mux, contentDir: contentDir, templater: tmpl, rawEvents: rawEvents, accts: accts, etsy: etsy, auth: auth, // TODO: shouldn't this ACTUALLY be a middleware? // - only try to make this an actual middleware AFTER all the routers are broken out, so that way how the middleware is supposed to work can be known authMiddleware: middleware.NewAuth( logger.WithGroup("auth-middleware"), auth, auth_api.NewLoginURL, accts, ), } // kind of a dumb way to capture routes for webpages wpr := templates_api.NewWebpageRouter( s.log.WithGroup("templates"), contentDir, tmpl, rawEvents, accts, etsy, s.authMiddleware, ) mux.AddMiddleware( func(fn response.HandlerFunc) response.HandlerFunc { return func(r *http.Request) (response.Response, error) { res, err := fn(r) if err == nil || !errors.Is(err, router.ErrHandlerNotFound) { return res, err } renderWebPage, pathParams, found := wpr.Handler(r) if !found { return nil, response.NotFound() } for k, v := range pathParams { r.SetPathValue(k, v) } return renderWebPage(r) } }, ) // api endpoints mux.Route("/auth", auth_api.NewLoginSubrouter(s.log.WithGroup("/auth"), auth)) mux.Route("/accounts", accounts_api.NewAccountSubrouter( s.log.WithGroup("/accounts"), s.accts, s.authMiddleware, )) // api webhooks (TODO: make a router for these) webhookHandler := http.StripPrefix("/webhooks", webhooks.New( logger.WithGroup("webhooks"), rawEvents, etsy, webhooks.Config{ Etsy: etsy_webhooks.Config{ OAuthRedirectURIWithAcctIDParam: "/oauth/account/{acctID}/auth_code", }, }, )) mux.Mux.Handle("GET /webhooks/", webhookHandler) mux.Mux.Handle("POST /webhooks/", webhookHandler) mux.Mux.Handle("PUT /webhooks/", webhookHandler) // webpage content // non-html content: scripts, styles, images, etc scfs := http.FileServer(http.Dir(contentDir + "/scripts")) mux.Mux.Handle("GET /scripts/", http.StripPrefix("/scripts", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/javascript") if path.Ext(r.URL.Path) == ".gz" { w.Header().Set("Content-Encoding", "gzip") } scfs.ServeHTTP(w, r) }))) mux.Mux.Handle("GET /styles/", http.StripPrefix("/styles", http.FileServer(http.Dir(contentDir+"/styles")))) mux.Mux.Handle("GET /favicon/", http.StripPrefix("/favicon", http.FileServer(http.Dir(contentDir+"/favicon")))) // html mux.Route("/", wpr) return s }