Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user