routing
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppAddr string
|
||||
AppEnv string
|
||||
AppDBDSN string
|
||||
AssetManifestPath string
|
||||
PrestaShopBaseURL string
|
||||
PrestaShopProxyTarget string
|
||||
PrestaShopVersion string
|
||||
PrestaShopCookieKey string
|
||||
PrestaShopCookieIV string
|
||||
PrestaShopCookieName string
|
||||
PrestaShopDBDSN string
|
||||
PrestaShopDBDialect string
|
||||
PrestaShopTablePrefix string
|
||||
PrestaShopProjectRoot string
|
||||
PrestaShopBootstrap string
|
||||
RouteOwnershipConfig string
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
if err := loadDotEnv(".env"); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
AppAddr: envOr("APP_ADDR", ":8080"),
|
||||
AppEnv: envOr("APP_ENV", "development"),
|
||||
AppDBDSN: firstNonEmpty(os.Getenv("APP_DB_DSN"), os.Getenv("PRESTASHOP_DB_DSN"), dbDSNFromEnv()),
|
||||
AssetManifestPath: envOr("ASSET_MANIFEST_PATH", "web/dist/manifest.json"),
|
||||
PrestaShopBaseURL: os.Getenv("PRESTASHOP_BASE_URL"),
|
||||
PrestaShopProxyTarget: os.Getenv("PRESTASHOP_PROXY_TARGET"),
|
||||
PrestaShopVersion: envOr("PRESTASHOP_VERSION", "1.7.3"),
|
||||
PrestaShopCookieKey: os.Getenv("PRESTASHOP_COOKIE_KEY"),
|
||||
PrestaShopCookieIV: os.Getenv("PRESTASHOP_COOKIE_IV"),
|
||||
PrestaShopCookieName: os.Getenv("PRESTASHOP_COOKIE_NAME"),
|
||||
PrestaShopDBDSN: firstNonEmpty(os.Getenv("PRESTASHOP_DB_DSN"), dbDSNFromEnv()),
|
||||
PrestaShopDBDialect: envOr("PRESTASHOP_DB_DIALECT", "mariadb"),
|
||||
PrestaShopTablePrefix: firstNonEmpty(os.Getenv("PRESTASHOP_TABLE_PREFIX"), os.Getenv("DB_PREFIX"), "ps_"),
|
||||
PrestaShopProjectRoot: os.Getenv("PRESTASHOP_PROJECT_ROOT"),
|
||||
PrestaShopBootstrap: os.Getenv("PRESTASHOP_BOOTSTRAP_PATH"),
|
||||
RouteOwnershipConfig: envOr("ROUTE_OWNERSHIP_CONFIG", "/product/"),
|
||||
}
|
||||
|
||||
if err := cfg.bootstrap(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
if cfg.PrestaShopProxyTarget == "" {
|
||||
return Config{}, errors.New("PRESTASHOP_PROXY_TARGET is required")
|
||||
}
|
||||
if cfg.PrestaShopProjectRoot == "" && cfg.PrestaShopBootstrap == "" && cfg.PrestaShopCookieKey == "" {
|
||||
return Config{}, errors.New("prestashop cookie configuration is incomplete")
|
||||
}
|
||||
if cfg.PrestaShopDBDSN == "" {
|
||||
return Config{}, errors.New("PRESTASHOP_DB_DSN is required")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func loadDotEnv(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("open %s: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "export ") {
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
key = strings.TrimSpace(key)
|
||||
value = strings.TrimSpace(value)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
value = strings.Trim(value, `"'`)
|
||||
if _, exists := os.LookupEnv(key); exists {
|
||||
continue
|
||||
}
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
return fmt.Errorf("set env %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("scan %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) CookieConfig() pscookie.Config {
|
||||
return pscookie.Config{
|
||||
Version: c.PrestaShopVersion,
|
||||
CookieName: c.DeriveCookieName(""),
|
||||
CookieKey: c.PrestaShopCookieKey,
|
||||
CookieIV: c.PrestaShopCookieIV,
|
||||
ProjectRoot: c.PrestaShopProjectRoot,
|
||||
BootstrapPath: c.PrestaShopBootstrap,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) DeriveCookieName(host string) string {
|
||||
if c.PrestaShopCookieName != "" {
|
||||
return c.PrestaShopCookieName
|
||||
}
|
||||
|
||||
domain := normalizedCookieDomain(host)
|
||||
if domain == "" {
|
||||
domain = normalizedCookieDomain(c.PrestaShopBaseURL)
|
||||
}
|
||||
if domain == "" {
|
||||
domain = normalizedCookieDomain(c.PrestaShopProxyTarget)
|
||||
}
|
||||
|
||||
sum := md5.Sum([]byte(c.PrestaShopVersion + "PrestaShop" + domain))
|
||||
return fmt.Sprintf("PrestaShop-%x", sum)
|
||||
}
|
||||
|
||||
func (c *Config) bootstrap() error {
|
||||
if c.PrestaShopProjectRoot == "" && c.PrestaShopBootstrap != "" {
|
||||
c.PrestaShopProjectRoot = filepath.Dir(filepath.Dir(c.PrestaShopBootstrap))
|
||||
}
|
||||
|
||||
if c.PrestaShopProjectRoot == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
settings := filepath.Join(c.PrestaShopProjectRoot, "config", "settings.inc.php")
|
||||
if data, err := os.ReadFile(settings); err == nil {
|
||||
if c.PrestaShopCookieKey == "" {
|
||||
c.PrestaShopCookieKey = parseDefine(string(data), "_COOKIE_KEY_")
|
||||
}
|
||||
if c.PrestaShopCookieIV == "" {
|
||||
c.PrestaShopCookieIV = parseDefine(string(data), "_COOKIE_IV_")
|
||||
}
|
||||
if c.PrestaShopTablePrefix == "ps_" {
|
||||
if prefix := parseDefine(string(data), "_DB_PREFIX_"); prefix != "" {
|
||||
c.PrestaShopTablePrefix = prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameters := filepath.Join(c.PrestaShopProjectRoot, "app", "config", "parameters.php")
|
||||
if data, err := os.ReadFile(parameters); err == nil {
|
||||
values := parsePHPParameters(string(data))
|
||||
if c.PrestaShopDBDSN == "" {
|
||||
c.PrestaShopDBDSN = mysqlDSN(values["database_host"], values["database_port"], values["database_name"], values["database_user"], values["database_password"])
|
||||
}
|
||||
if c.PrestaShopCookieKey == "" {
|
||||
c.PrestaShopCookieKey = values["secret"]
|
||||
}
|
||||
}
|
||||
|
||||
if c.PrestaShopBootstrap == "" {
|
||||
c.PrestaShopBootstrap = filepath.Join(c.PrestaShopProjectRoot, "config", "config.inc.php")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDefine(input, key string) string {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`define\('%s',\s*'([^']*)'`, regexp.QuoteMeta(key)))
|
||||
matches := re.FindStringSubmatch(input)
|
||||
if len(matches) < 2 {
|
||||
return ""
|
||||
}
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
func parsePHPParameters(input string) map[string]string {
|
||||
out := map[string]string{}
|
||||
re := regexp.MustCompile(`'([^']+)'\s*=>\s*'([^']*)'`)
|
||||
for _, match := range re.FindAllStringSubmatch(input, -1) {
|
||||
out[match[1]] = match[2]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mysqlDSN(host, port, db, user, pass string) string {
|
||||
if host == "" || db == "" || user == "" {
|
||||
return ""
|
||||
}
|
||||
if port == "" {
|
||||
port = "3306"
|
||||
}
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=UTC", user, pass, host, port, db)
|
||||
}
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizedCookieDomain(input string) string {
|
||||
value := strings.TrimSpace(input)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(value, "://") {
|
||||
if parsed, err := url.Parse(value); err == nil {
|
||||
value = parsed.Hostname()
|
||||
}
|
||||
}
|
||||
if host, _, err := net.SplitHostPort(value); err == nil {
|
||||
value = host
|
||||
}
|
||||
value = strings.TrimPrefix(strings.ToLower(value), ".")
|
||||
value = strings.TrimPrefix(value, "www.")
|
||||
return value
|
||||
}
|
||||
|
||||
func dbDSNFromEnv() string {
|
||||
return mysqlDSN(
|
||||
firstNonEmpty(os.Getenv("PRESTASHOP_DB_HOST"), os.Getenv("DB_HOST")),
|
||||
firstNonEmpty(os.Getenv("PRESTASHOP_DB_PORT"), os.Getenv("DB_PORT")),
|
||||
firstNonEmpty(os.Getenv("PRESTASHOP_DB_NAME"), os.Getenv("DB_NAME")),
|
||||
firstNonEmpty(os.Getenv("PRESTASHOP_DB_USER"), os.Getenv("DB_USER")),
|
||||
firstNonEmpty(os.Getenv("PRESTASHOP_DB_PASS"), os.Getenv("DB_PASS")),
|
||||
)
|
||||
}
|
||||
|
||||
type RouteOwnership struct {
|
||||
ProductPrefixes []string `json:"product_prefixes"`
|
||||
}
|
||||
|
||||
func (c Config) ParseRouteOwnership() RouteOwnership {
|
||||
if strings.HasPrefix(strings.TrimSpace(c.RouteOwnershipConfig), "{") {
|
||||
var parsed RouteOwnership
|
||||
if err := json.Unmarshal([]byte(c.RouteOwnershipConfig), &parsed); err == nil && len(parsed.ProductPrefixes) > 0 {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
return RouteOwnership{
|
||||
ProductPrefixes: []string{c.RouteOwnershipConfig},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user