package server import ( "context" "net/http" "path" "strings" "github.com/gin-gonic/gin" "ruben/inventory2/internal/domains/accounts" "ruben/inventory2/internal/domains/authentication" etsy_platform "ruben/inventory2/internal/domains/platforms/etsy" "ruben/inventory2/internal/domains/raw_events" "ruben/inventory2/internal/logging" accounts_api "ruben/inventory2/internal/server/api/accounts" auth_api "ruben/inventory2/internal/server/api/auth" sse_api "ruben/inventory2/internal/server/api/sse" templates_api "ruben/inventory2/internal/server/api/templates" "ruben/inventory2/internal/server/api/webhooks" etsy_webhooks "ruben/inventory2/internal/server/api/webhooks/etsy" "ruben/inventory2/internal/server/middleware" "ruben/inventory2/internal/server/response" "ruben/inventory2/internal/server/sse" ) // r gin.IRouter, // TODO: use this everywhere type Router struct { *gin.Engine sse *sse.Queue } func NewRouter( logger *logging.Logger, contentDir string, rawEvents *raw_events.Store, accts *accounts.Store, etsy *etsy_platform.Platform, auth *authentication.Authenticator, ) *Router { r := gin.Default() r.Use( response.HandleResponses, response.HandleErrors, ) // 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, ) // webpage content // non-html content: scripts, styles, images, etc r.Use(fileServer("/scripts", contentDir+"/scripts", func(c *gin.Context) { w := c.Writer w.Header().Set("Content-Type", "text/javascript") if path.Ext(c.Request.URL.Path) == ".gz" { w.Header().Set("Content-Encoding", "gzip") } })) r.Static("/styles", "./styles") r.Static("/favicon", "./favicon") r.Static("/images", "./images") // html (must be before the api endpoints, because the webpage middleware has to be installed beforehand r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "/ui") }) // kind of a dumb way to capture routes for webpages templates_api.SetupRoutes( logger.WithGroup("templates"), r.Group("/ui"), contentDir, rawEvents, accts, etsy, authMiddleware, ) // sse setup sq := sse.NewQueue() unp := middleware.NewUpdateNotificationPublisher( logger.WithGroup("update.notification.publisher"), sq, ) // api endpoints api := r.Group("/api") apiLogger := logger.WithGroup("/api") unp = unp.Trim("/api") auth_api.Routes( api.Group("/auth"), apiLogger.WithGroup("/auth"), auth, ) sse_api.Routes( api.Group("/events"), apiLogger.WithGroup("/events"), sq, authMiddleware, ) accounts_api.Routes( api.Group("/accounts", authMiddleware.AuthenticateAndAddIdentityGin()), apiLogger.WithGroup("/accounts"), accts, unp.Group("/accounts"), ) // api webhooks (TODO: make a router for these) webhooks.Webhooks( api.Group("/webhooks"), logger.WithGroup("webhooks"), rawEvents, etsy, webhooks.Config{ Etsy: etsy_webhooks.Config{ OAuthRedirectURIWithAcctIDParam: "/oauth/account/{acctID}/auth_code", }, }, ) return &Router{ Engine: r, sse: sq, } } func (r *Router) RunSSE(ctx context.Context) error { return r.sse.Start(ctx) } func fileServer(urlPrefix, dir string, beforeServe func(c *gin.Context)) gin.HandlerFunc { scfs := http.StripPrefix(urlPrefix, http.FileServer(http.Dir(dir))) return func(c *gin.Context) { r := c.Request if r.URL.Path == urlPrefix || strings.HasPrefix(r.URL.Path, path.Join(urlPrefix, "/")) { if beforeServe != nil { beforeServe(c) } scfs.ServeHTTP(c.Writer, r) c.Abort() } // TODO: need a c.Next()? } }