296 lines
7.9 KiB
Go
296 lines
7.9 KiB
Go
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/md5"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
pscookie "git.ma-al.com/goc_marek/ps_shop/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 := fallbackCookieHashDomain(host)
|
|
if domain == "" {
|
|
domain = fallbackCookieHashDomain(c.PrestaShopBaseURL)
|
|
}
|
|
if domain == "" {
|
|
domain = fallbackCookieHashDomain(c.PrestaShopProxyTarget)
|
|
}
|
|
|
|
sum := md5.Sum([]byte(c.PrestaShopVersion + "ps-s1" + 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 fallbackCookieHashDomain(input string) string {
|
|
value := normalizedCookieDomain(input)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
if net.ParseIP(value) != nil || !strings.Contains(value, ".") {
|
|
return ""
|
|
}
|
|
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},
|
|
}
|
|
}
|