77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
package billing
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"errors"
|
|||
|
|
"fmt"
|
|||
|
|
"strings"
|
|||
|
|
|
|||
|
|
"github.com/google/uuid"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// FeatureKeyFromError extracts the feature key from an ErrFeatureDisabled wrap
|
|||
|
|
// ("feature_disabled: marketing.campaigns.generate_ai").
|
|||
|
|
func FeatureKeyFromError(err error) string {
|
|||
|
|
if err == nil || !errors.Is(err, ErrFeatureDisabled) {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
msg := err.Error()
|
|||
|
|
const prefix = "feature_disabled:"
|
|||
|
|
idx := strings.Index(strings.ToLower(msg), prefix)
|
|||
|
|
if idx < 0 {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return strings.TrimSpace(msg[idx+len(prefix):])
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// FeatureKeysForProcessingType maps a processing job type to registry keys that
|
|||
|
|
// must be effective before StartJob may proceed.
|
|||
|
|
func FeatureKeysForProcessingType(processingType string) []string {
|
|||
|
|
switch strings.ToLower(strings.TrimSpace(processingType)) {
|
|||
|
|
case "title":
|
|||
|
|
return []string{"capability.ai_processing", "catalog.products.process_ai_titles"}
|
|||
|
|
case "description":
|
|||
|
|
return []string{"capability.ai_processing", "catalog.products.process_ai_descriptions"}
|
|||
|
|
case "enhance", "enhance_only", "enhance-only", "seo", "seo_ai":
|
|||
|
|
return []string{"capability.ai_processing"}
|
|||
|
|
case "eprel", "eprel_only":
|
|||
|
|
return []string{"capability.eprel"}
|
|||
|
|
case "email_campaign", "email_campaign_ai", "campaign_ai":
|
|||
|
|
return []string{"capability.campaign_ai", "marketing.campaigns.generate_ai"}
|
|||
|
|
case "normalize", "specs", "fill", "categories", "attributes", "full", "":
|
|||
|
|
return []string{"capability.normalize_specs_fill"}
|
|||
|
|
default:
|
|||
|
|
return []string{"capability.normalize_specs_fill"}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AssertFeatures fails closed on the first disabled key.
|
|||
|
|
// Loads Capabilities once for the whole key set (avoids N×CapabilitiesForCompany).
|
|||
|
|
func (s *Service) AssertFeatures(ctx context.Context, companyID uuid.UUID, keys ...string) error {
|
|||
|
|
if len(keys) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
caps, err := s.CapabilitiesForCompany(ctx, companyID)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
for _, key := range keys {
|
|||
|
|
key = strings.TrimSpace(key)
|
|||
|
|
if key == "" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if caps.Features == nil || !caps.Features[key] {
|
|||
|
|
return fmt.Errorf("%w: %s", ErrFeatureDisabled, key)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AssertProcessingFeatures enforces plan ∩ global feature keys for a job type.
|
|||
|
|
func (s *Service) AssertProcessingFeatures(ctx context.Context, companyID uuid.UUID, processingType string) error {
|
|||
|
|
if s == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
return s.AssertFeatures(ctx, companyID, FeatureKeysForProcessingType(processingType)...)
|
|||
|
|
}
|