Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
package platformsettings
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/billing"
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/eprel"
|
|
)
|
|
|
|
// ResolveStripe merges platform Values over env/base StripeConfig.
|
|
// Non-empty DB values win; price IDs use "starter:monthly" keys.
|
|
func (s *Service) ResolveStripe(ctx context.Context, base billing.StripeConfig) (billing.StripeConfig, error) {
|
|
out := base
|
|
if out.PriceIDs == nil {
|
|
out.PriceIDs = map[string]string{}
|
|
} else {
|
|
cp := make(map[string]string, len(out.PriceIDs))
|
|
for k, v := range out.PriceIDs {
|
|
cp[k] = v
|
|
}
|
|
out.PriceIDs = cp
|
|
}
|
|
|
|
if v, ok, err := s.GetKV(ctx, KeyStripeSecretKey); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.SecretKey = v
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyStripeWebhookSecret); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.WebhookSecret = v
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyStripeMock); err != nil {
|
|
return out, err
|
|
} else if ok {
|
|
out.ForceMock = parseTruthy(v)
|
|
}
|
|
|
|
priceKeys := []struct {
|
|
setting string
|
|
price string
|
|
}{
|
|
{KeyStripePriceStarterMo, "starter:monthly"},
|
|
{KeyStripePriceStarterYr, "starter:yearly"},
|
|
{KeyStripePricePlusMo, "plus:monthly"},
|
|
{KeyStripePricePlusYr, "plus:yearly"},
|
|
{KeyStripePriceGrowthMo, "growth:monthly"},
|
|
{KeyStripePriceGrowthYr, "growth:yearly"},
|
|
{KeyStripePriceBizMo, "business:monthly"},
|
|
{KeyStripePriceBizYr, "business:yearly"},
|
|
{KeyStripePriceScaleMo, "scale:monthly"},
|
|
{KeyStripePriceScaleYr, "scale:yearly"},
|
|
}
|
|
for _, p := range priceKeys {
|
|
if v, ok, err := s.GetKV(ctx, p.setting); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.PriceIDs[p.price] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
for _, pack := range billing.DefaultCreditPacks() {
|
|
setting := billing.CreditPackSettingsKey(pack.ID)
|
|
if v, ok, err := s.GetKV(ctx, setting); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.PriceIDs[billing.CreditPackPriceKey(pack.ID)] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// EPRELOptions is the runtime EPREL client config after settings merge.
|
|
type EPRELOptions struct {
|
|
Enabled bool
|
|
BaseURL string
|
|
Timeout time.Duration
|
|
FicheLanguage string
|
|
APIKey string
|
|
}
|
|
|
|
// ResolveEPREL merges Values over EnvConfig EPREL fields (when set) and defaults.
|
|
// Enrichment defaults on (EPREL_ENABLED env default true); admin eprel.enabled overrides when set.
|
|
// API key is optional — the public EU EPREL API does not require authentication.
|
|
func (s *Service) ResolveEPREL(ctx context.Context) (EPRELOptions, error) {
|
|
out := EPRELOptions{
|
|
Enabled: s.Env.EPRELEnabled,
|
|
BaseURL: s.Env.EPRELBaseURL,
|
|
Timeout: s.Env.EPRELTimeout,
|
|
FicheLanguage: s.Env.EPRELFicheLanguage,
|
|
APIKey: s.Env.EPRELAPIKey,
|
|
}
|
|
if out.BaseURL == "" {
|
|
out.BaseURL = "https://eprel.ec.europa.eu/api"
|
|
}
|
|
if out.Timeout <= 0 {
|
|
out.Timeout = 10 * time.Second
|
|
}
|
|
if out.FicheLanguage == "" {
|
|
out.FicheLanguage = "EN"
|
|
}
|
|
|
|
if v, ok, err := s.GetKV(ctx, KeyEPRELEnabled); err != nil {
|
|
return out, err
|
|
} else if ok {
|
|
out.Enabled = parseTruthy(v)
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyEPRELBaseURL); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.BaseURL = strings.TrimSpace(v)
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyEPRELTimeout); err != nil {
|
|
return out, err
|
|
} else if ok {
|
|
if d, okDur := parseDuration(v); okDur {
|
|
out.Timeout = d
|
|
}
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyEPRELFicheLanguage); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
if code, nerr := NormalizeEPRELFicheLanguage(v); nerr == nil {
|
|
out.FicheLanguage = code
|
|
}
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyEPRELAPIKey); err != nil {
|
|
return out, err
|
|
} else if ok {
|
|
out.APIKey = strings.TrimSpace(v)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// NewEPRELClient builds an EPREL client from ResolveEPREL.
|
|
func (s *Service) NewEPRELClient(ctx context.Context) (*eprel.Client, error) {
|
|
opts, err := s.ResolveEPREL(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return eprel.NewClient(eprel.Options{
|
|
Enabled: opts.Enabled,
|
|
BaseURL: opts.BaseURL,
|
|
Timeout: opts.Timeout,
|
|
FicheLanguage: opts.FicheLanguage,
|
|
APIKey: opts.APIKey,
|
|
}), nil
|
|
}
|
|
|
|
// ResolvedPinecone is runtime Pinecone config after settings merge (plaintext key — never log).
|
|
type ResolvedPinecone struct {
|
|
APIKey string
|
|
Host string
|
|
Namespace string
|
|
}
|
|
|
|
// Configured reports whether vector features can run (key + host required).
|
|
func (r ResolvedPinecone) Configured() bool {
|
|
return strings.TrimSpace(r.APIKey) != "" && strings.TrimSpace(r.Host) != ""
|
|
}
|
|
|
|
// ResolvePinecone merges Values over EnvConfig Pinecone fields (DB wins when set).
|
|
func (s *Service) ResolvePinecone(ctx context.Context) (ResolvedPinecone, error) {
|
|
out := ResolvedPinecone{
|
|
APIKey: strings.TrimSpace(s.Env.PineconeAPIKey),
|
|
Host: strings.TrimSpace(s.Env.PineconeHost),
|
|
Namespace: strings.TrimSpace(s.Env.PineconeNamespace),
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyPineconeAPIKey); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.APIKey = strings.TrimSpace(v)
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyPineconeHost); err != nil {
|
|
return out, err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
out.Host = strings.TrimSpace(v)
|
|
}
|
|
if v, ok, err := s.GetKV(ctx, KeyPineconeNamespace); err != nil {
|
|
return out, err
|
|
} else if ok {
|
|
out.Namespace = strings.TrimSpace(v)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ResolveFeedPrivateAllowlist returns settings CSV, falling back to env.
|
|
func (s *Service) ResolveFeedPrivateAllowlist(ctx context.Context) (string, error) {
|
|
if v, ok, err := s.GetKV(ctx, KeyFeedPrivateAllowlist); err != nil {
|
|
return "", err
|
|
} else if ok && strings.TrimSpace(v) != "" {
|
|
return strings.TrimSpace(v), nil
|
|
}
|
|
if strings.TrimSpace(s.Env.FeedPrivateAllowlist) != "" {
|
|
return strings.TrimSpace(s.Env.FeedPrivateAllowlist), nil
|
|
}
|
|
return strings.TrimSpace(os.Getenv("FEED_URL_PRIVATE_ALLOWLIST")), nil
|
|
}
|
|
|
|
func parseDuration(v string) (time.Duration, bool) {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return 0, false
|
|
}
|
|
if d, err := time.ParseDuration(v); err == nil {
|
|
return d, true
|
|
}
|
|
if n, err := strconv.Atoi(v); err == nil && n >= 0 {
|
|
return time.Duration(n) * time.Second, true
|
|
}
|
|
return 0, false
|
|
}
|