228 lines
6.7 KiB
Go
228 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
"git.ma-al.com/goc_marek/ps_shop/internal/assets"
|
|
"git.ma-al.com/goc_marek/ps_shop/internal/http/handlers"
|
|
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
|
|
httpproxy "git.ma-al.com/goc_marek/ps_shop/internal/http/proxy"
|
|
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
|
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
|
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
|
|
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
|
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
|
|
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
|
|
pssession "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/session"
|
|
"git.ma-al.com/goc_marek/ps_shop/internal/render"
|
|
"git.ma-al.com/goc_marek/ps_shop/internal/store"
|
|
)
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
slog.Error("fatal", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
|
slog.SetDefault(logger)
|
|
|
|
cfg, err := psconfig.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("load config: %w", err)
|
|
}
|
|
|
|
appStore, err := store.Open(cfg.PrestaShopDBDialect, cfg.AppDBDSN)
|
|
if err != nil {
|
|
return fmt.Errorf("open app db: %w", err)
|
|
}
|
|
defer closeDB("app db", appStore)
|
|
|
|
prestaDB, err := store.OpenPresta(cfg.PrestaShopDBDialect, cfg.PrestaShopDBDSN)
|
|
if err != nil {
|
|
return fmt.Errorf("open prestashop db: %w", err)
|
|
}
|
|
defer closeDB("prestashop db", prestaDB)
|
|
|
|
assetManifest, err := assets.LoadManifest(cfg.AssetManifestPath)
|
|
if err != nil {
|
|
return fmt.Errorf("load asset manifest: %w", err)
|
|
}
|
|
|
|
cookieCodec, err := pscookie.NewCodec(cfg.CookieConfig())
|
|
if err != nil {
|
|
return fmt.Errorf("create cookie codec: %w", err)
|
|
}
|
|
|
|
productService := pscatalog.NewService(prestaDB, cfg.PrestaShopTablePrefix)
|
|
customerService := pscustomer.NewService(prestaDB, cfg.PrestaShopTablePrefix)
|
|
cartService := pscart.NewService(prestaDB, cfg.PrestaShopTablePrefix)
|
|
routeService := psroutes.NewService(prestaDB, cfg.PrestaShopTablePrefix)
|
|
sessionService := pssession.NewService(
|
|
prestaDB,
|
|
cfg.PrestaShopTablePrefix,
|
|
cfg.PrestaShopVersion,
|
|
cfg.PrestaShopCookieName,
|
|
cfg.DomainCookie,
|
|
)
|
|
productRoute, err := routeService.LoadProductRoute(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("load product route rule: %w", err)
|
|
}
|
|
categoryRoute, err := routeService.LoadCategoryRoute(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("load category route rule: %w", err)
|
|
}
|
|
productHandler := handlers.NewProductHandler(
|
|
productService,
|
|
customerService,
|
|
cartService,
|
|
render.New(assetManifest),
|
|
cfg,
|
|
categoryRoute,
|
|
)
|
|
categoryHandler := handlers.NewCategoryHandler(
|
|
productService,
|
|
customerService,
|
|
cartService,
|
|
render.New(assetManifest),
|
|
cfg,
|
|
productRoute,
|
|
categoryRoute,
|
|
)
|
|
|
|
proxyHandler, err := httpproxy.New(cfg.PrestaShopProxyTarget)
|
|
if err != nil {
|
|
return fmt.Errorf("create reverse proxy: %w", err)
|
|
}
|
|
|
|
e := echo.New()
|
|
e.HideBanner = true
|
|
e.HidePort = true
|
|
e.HTTPErrorHandler = appmiddleware.HTTPErrorHandler(logger)
|
|
e.Use(
|
|
appmiddleware.RequestID(),
|
|
appmiddleware.RealIP(),
|
|
appmiddleware.AccessLog(logger),
|
|
appmiddleware.Recover(logger),
|
|
appmiddleware.Session(cfg, cookieCodec, sessionService, productService, psroutes.CombineMatchers(productRoute, categoryRoute)),
|
|
)
|
|
|
|
e.Static("/dist", "web/dist")
|
|
e.GET("/healthz", handlers.Healthz())
|
|
e.GET("/readyz", handlers.Readyz(appStore, prestaDB, cfg.PrestaShopProxyTarget))
|
|
e.Match([]string{http.MethodGet, http.MethodPost}, "/debug/cookie/decode", handlers.DecodeCookie(cookieCodec))
|
|
e.GET("/*", func(c echo.Context) error {
|
|
productMatch, productOK := productRoute.MatchInfo(c.Request().URL.Path)
|
|
categoryMatch, categoryOK := categoryRoute.MatchInfo(c.Request().URL.Path)
|
|
productSlug := ""
|
|
productID := int64(0)
|
|
if productOK {
|
|
productSlug = productMatch.Slug
|
|
productID = productMatch.ID
|
|
}
|
|
categorySlug := ""
|
|
categoryID := int64(0)
|
|
if categoryOK {
|
|
categorySlug = categoryMatch.Slug
|
|
categoryID = categoryMatch.ID
|
|
}
|
|
slog.Info("route dispatch probe",
|
|
"method", c.Request().Method,
|
|
"path", c.Request().URL.Path,
|
|
"product_match", productOK,
|
|
"product_id", productID,
|
|
"product_slug", productSlug,
|
|
"category_match", categoryOK,
|
|
"category_id", categoryID,
|
|
"category_slug", categorySlug,
|
|
)
|
|
if !productOK {
|
|
if categoryOK {
|
|
slog.Info("route dispatch", "owner", "go-category", "method", c.Request().Method, "path", c.Request().URL.Path, "slug", categorySlug, "id", categoryID)
|
|
handlers.SetCategoryID(c, categoryID)
|
|
handlers.SetCategorySlug(c, categorySlug)
|
|
return categoryHandler.Show(c)
|
|
}
|
|
slog.Info("route dispatch", "owner", "prestashop", "method", c.Request().Method, "path", c.Request().URL.Path)
|
|
return proxyHandler.Handle(c)
|
|
}
|
|
slog.Info("route dispatch", "owner", "go", "method", c.Request().Method, "path", c.Request().URL.Path, "id", productID, "slug", productSlug)
|
|
handlers.SetProductID(c, productID)
|
|
handlers.SetProductSlug(c, productSlug)
|
|
return productHandler.Show(c)
|
|
})
|
|
e.Match([]string{
|
|
http.MethodPost,
|
|
http.MethodPut,
|
|
http.MethodPatch,
|
|
http.MethodDelete,
|
|
http.MethodHead,
|
|
http.MethodOptions,
|
|
http.MethodConnect,
|
|
http.MethodTrace,
|
|
}, "/*", func(c echo.Context) error {
|
|
slog.Info("route dispatch", "owner", "prestashop", "method", c.Request().Method, "path", c.Request().URL.Path)
|
|
return proxyHandler.Handle(c)
|
|
})
|
|
|
|
server := &http.Server{
|
|
Addr: cfg.AppAddr,
|
|
Handler: e,
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
shutdownErrCh := make(chan error, 1)
|
|
go func() {
|
|
<-ctx.Done()
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
err := e.Shutdown(shutdownCtx)
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
err = nil
|
|
}
|
|
shutdownErrCh <- err
|
|
}()
|
|
|
|
slog.Info("starting server", "addr", cfg.AppAddr, "proxy_target", cfg.PrestaShopProxyTarget)
|
|
if err := e.StartServer(server); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return err
|
|
}
|
|
if ctx.Err() != nil {
|
|
if err := <-shutdownErrCh; err != nil {
|
|
return fmt.Errorf("shutdown server: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func closeDB(name string, db interface{ DB() (*sql.DB, error) }) {
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
slog.Error("resolve db handle", "name", name, "error", err)
|
|
return
|
|
}
|
|
if err := sqlDB.Close(); err != nil {
|
|
slog.Error("close db", "name", name, "error", err)
|
|
}
|
|
}
|