diff --git a/apps/carrier-connector/internal/whitelabel/service.go b/apps/carrier-connector/internal/whitelabel/service.go new file mode 100644 index 0000000..83206d1 --- /dev/null +++ b/apps/carrier-connector/internal/whitelabel/service.go @@ -0,0 +1,99 @@ +package whitelabel + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// Service provides whitelabel management operations +type Service struct { + db *gorm.DB + logger *logrus.Logger +} + +// NewService creates a new whitelabel service +func NewService(db *gorm.DB, logger *logrus.Logger) *Service { + return &Service{db: db, logger: logger} +} + +// CreateBranding creates a new branding configuration +func (s *Service) CreateBranding(ctx context.Context, config *BrandingConfig) error { + if err := s.db.WithContext(ctx).Create(config).Error; err != nil { + s.logger.WithError(err).Error("Failed to create branding config") + return fmt.Errorf("failed to create branding: %w", err) + } + s.logger.WithField("tenant_id", config.TenantID).Info("Branding config created") + return nil +} + +// GetBranding retrieves branding by tenant ID +func (s *Service) GetBranding(ctx context.Context, tenantID string) (*BrandingConfig, error) { + var config BrandingConfig + if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil { + return nil, fmt.Errorf("branding not found: %w", err) + } + return &config, nil +} + +// GetBrandingByDomain retrieves branding by custom domain +func (s *Service) GetBrandingByDomain(ctx context.Context, domain string) (*BrandingConfig, error) { + var config BrandingConfig + if err := s.db.WithContext(ctx).Where("custom_domain = ? AND is_active = ?", domain, true).First(&config).Error; err != nil { + return nil, fmt.Errorf("branding not found for domain: %w", err) + } + return &config, nil +} + +// UpdateBranding updates branding configuration +func (s *Service) UpdateBranding(ctx context.Context, config *BrandingConfig) error { + if err := s.db.WithContext(ctx).Save(config).Error; err != nil { + return fmt.Errorf("failed to update branding: %w", err) + } + return nil +} + +// CreatePartnerConfig creates partner configuration +func (s *Service) CreatePartnerConfig(ctx context.Context, config *PartnerConfig) error { + if err := s.db.WithContext(ctx).Create(config).Error; err != nil { + return fmt.Errorf("failed to create partner config: %w", err) + } + return nil +} + +// GetPartnerConfig retrieves partner configuration +func (s *Service) GetPartnerConfig(ctx context.Context, tenantID string) (*PartnerConfig, error) { + var config PartnerConfig + if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil { + return nil, fmt.Errorf("partner config not found: %w", err) + } + return &config, nil +} + +// CreateEmailTemplate creates a custom email template +func (s *Service) CreateEmailTemplate(ctx context.Context, template *EmailTemplate) error { + if err := s.db.WithContext(ctx).Create(template).Error; err != nil { + return fmt.Errorf("failed to create email template: %w", err) + } + return nil +} + +// GetEmailTemplate retrieves an email template +func (s *Service) GetEmailTemplate(ctx context.Context, tenantID, templateKey string) (*EmailTemplate, error) { + var template EmailTemplate + if err := s.db.WithContext(ctx).Where("tenant_id = ? AND template_key = ? AND is_active = ?", tenantID, templateKey, true).First(&template).Error; err != nil { + return nil, fmt.Errorf("email template not found: %w", err) + } + return &template, nil +} + +// ListEmailTemplates lists all templates for a tenant +func (s *Service) ListEmailTemplates(ctx context.Context, tenantID string) ([]*EmailTemplate, error) { + var templates []*EmailTemplate + if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Find(&templates).Error; err != nil { + return nil, fmt.Errorf("failed to list templates: %w", err) + } + return templates, nil +} diff --git a/apps/carrier-connector/internal/whitelabel/types.go b/apps/carrier-connector/internal/whitelabel/types.go new file mode 100644 index 0000000..0cd67e5 --- /dev/null +++ b/apps/carrier-connector/internal/whitelabel/types.go @@ -0,0 +1,85 @@ +package whitelabel + +import "time" + +// BrandingConfig defines partner branding configuration +type BrandingConfig struct { + ID string `json:"id" gorm:"primaryKey"` + TenantID string `json:"tenant_id" gorm:"index"` + CompanyName string `json:"company_name"` + LogoURL string `json:"logo_url"` + FaviconURL string `json:"favicon_url"` + PrimaryColor string `json:"primary_color"` + SecondaryColor string `json:"secondary_color"` + AccentColor string `json:"accent_color"` + FontFamily string `json:"font_family"` + CustomCSS string `json:"custom_css"` + CustomDomain string `json:"custom_domain" gorm:"uniqueIndex"` + EmailFromName string `json:"email_from_name"` + EmailFromAddr string `json:"email_from_address"` + SupportEmail string `json:"support_email"` + SupportPhone string `json:"support_phone"` + TermsURL string `json:"terms_url"` + PrivacyURL string `json:"privacy_url"` + FooterText string `json:"footer_text"` + SocialLinks map[string]string `json:"social_links" gorm:"serializer:json"` + Features FeatureFlags `json:"features" gorm:"embedded;embeddedPrefix:feature_"` + IsActive bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// FeatureFlags controls which features are enabled for a whitelabel partner +type FeatureFlags struct { + ShowPoweredBy bool `json:"show_powered_by"` + CustomEmailDomain bool `json:"custom_email_domain"` + CustomAPIDomain bool `json:"custom_api_domain"` + AdvancedAnalytics bool `json:"advanced_analytics"` + WhitelabelMobile bool `json:"whitelabel_mobile"` + CustomWebhooks bool `json:"custom_webhooks"` + APIDocsBranding bool `json:"api_docs_branding"` + MultiLanguage bool `json:"multi_language"` +} + +// EmailTemplate defines customizable email templates +type EmailTemplate struct { + ID string `json:"id" gorm:"primaryKey"` + TenantID string `json:"tenant_id" gorm:"index"` + TemplateKey string `json:"template_key" gorm:"index"` + Subject string `json:"subject"` + HTMLBody string `json:"html_body"` + TextBody string `json:"text_body"` + Variables []string `json:"variables" gorm:"serializer:json"` + IsActive bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// PartnerTier defines partnership levels with different capabilities +type PartnerTier string + +const ( + PartnerTierBasic PartnerTier = "basic" + PartnerTierProfession PartnerTier = "professional" + PartnerTierEnterprise PartnerTier = "enterprise" + PartnerTierPlatinum PartnerTier = "platinum" +) + +// PartnerConfig defines partner-specific configuration +type PartnerConfig struct { + ID string `json:"id" gorm:"primaryKey"` + TenantID string `json:"tenant_id" gorm:"uniqueIndex"` + Tier PartnerTier `json:"tier"` + RevenueSharePct float64 `json:"revenue_share_pct"` + MinMonthlyCommit float64 `json:"min_monthly_commit"` + MaxAPIRequests int64 `json:"max_api_requests"` + MaxProfiles int64 `json:"max_profiles"` + AllowedCountries []string `json:"allowed_countries" gorm:"serializer:json"` + AllowedCarriers []string `json:"allowed_carriers" gorm:"serializer:json"` + CustomPricing map[string]any `json:"custom_pricing" gorm:"serializer:json"` + ContractStartDate time.Time `json:"contract_start_date"` + ContractEndDate time.Time `json:"contract_end_date"` + IsActive bool `json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +}