feat: add brand library management features
- Introduced BrandLibrarySummary type to encapsulate brand library limits and usage. - Updated Brand interface to include keyword_count and question_count fields. - Implemented brand library limits in the backend, including max brands, keywords, and questions per keyword. - Added API endpoint to retrieve brand library summary. - Enhanced BrandsView.vue to display brand library usage and limits. - Implemented computed properties to manage brand, keyword, and question limits in the UI. - Updated mutation handlers to invalidate relevant queries upon creating/updating brands, keywords, and questions. - Added visual indicators for brand, keyword, and question limits in the UI. - Enhanced error handling for exceeding brand and keyword limits during creation.
This commit is contained in:
@@ -16,6 +16,7 @@ type Config struct {
|
||||
RabbitMQ RabbitMQConfig `mapstructure:"rabbitmq"`
|
||||
Scheduler SchedulerConfig `mapstructure:"scheduler"`
|
||||
MonitoringWorkers MonitoringConfig `mapstructure:"monitoring_workers"`
|
||||
BrandLibrary BrandLibraryConfig `mapstructure:"brand_library"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Qdrant QdrantConfig `mapstructure:"qdrant"`
|
||||
ObjectStorage ObjectStorageConfig `mapstructure:"object_storage"`
|
||||
@@ -103,6 +104,20 @@ type MonitoringConfig struct {
|
||||
ProjectionRebuildConcurrency int `mapstructure:"projection_rebuild_concurrency"`
|
||||
}
|
||||
|
||||
type BrandLibraryConfig struct {
|
||||
FreeBrandLimit int `mapstructure:"free_brand_limit"`
|
||||
PaidBrandLimit int `mapstructure:"paid_brand_limit"`
|
||||
MaxKeywords int `mapstructure:"max_keywords"`
|
||||
MaxQuestionsPerKeyword int `mapstructure:"max_questions_per_keyword"`
|
||||
}
|
||||
|
||||
func (c BrandLibraryConfig) BrandLimitForPlan(planCode string) int {
|
||||
if strings.EqualFold(strings.TrimSpace(planCode), "free") {
|
||||
return c.FreeBrandLimit
|
||||
}
|
||||
return c.PaidBrandLimit
|
||||
}
|
||||
|
||||
type QdrantConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
@@ -196,6 +211,7 @@ func Load(configPath string) (*Config, error) {
|
||||
normalizeRabbitMQConfig(&cfg.RabbitMQ)
|
||||
normalizeSchedulerConfig(&cfg.Scheduler)
|
||||
normalizeMonitoringConfig(&cfg.MonitoringWorkers)
|
||||
normalizeBrandLibraryConfig(&cfg.BrandLibrary)
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
@@ -398,6 +414,25 @@ func normalizeMonitoringConfig(cfg *MonitoringConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeBrandLibraryConfig(cfg *BrandLibraryConfig) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.FreeBrandLimit <= 0 {
|
||||
cfg.FreeBrandLimit = 1
|
||||
}
|
||||
if cfg.PaidBrandLimit <= 0 {
|
||||
cfg.PaidBrandLimit = 2
|
||||
}
|
||||
if cfg.MaxKeywords <= 0 {
|
||||
cfg.MaxKeywords = 5
|
||||
}
|
||||
if cfg.MaxQuestionsPerKeyword <= 0 {
|
||||
cfg.MaxQuestionsPerKeyword = 5
|
||||
}
|
||||
}
|
||||
|
||||
func lookupFirstNonEmptyEnv(keys ...string) (string, bool) {
|
||||
for _, key := range keys {
|
||||
if value, ok := lookupNonEmptyEnv(key); ok {
|
||||
|
||||
@@ -89,6 +89,30 @@ retrieval:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesBrandLibraryDefaults(t *testing.T) {
|
||||
configPath := writeTestConfig(t, `
|
||||
brand_library: {}
|
||||
`)
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
|
||||
if cfg.BrandLibrary.FreeBrandLimit != 1 {
|
||||
t.Fatalf("expected default free brand limit 1, got %d", cfg.BrandLibrary.FreeBrandLimit)
|
||||
}
|
||||
if cfg.BrandLibrary.PaidBrandLimit != 2 {
|
||||
t.Fatalf("expected default paid brand limit 2, got %d", cfg.BrandLibrary.PaidBrandLimit)
|
||||
}
|
||||
if cfg.BrandLibrary.MaxKeywords != 5 {
|
||||
t.Fatalf("expected default max keywords 5, got %d", cfg.BrandLibrary.MaxKeywords)
|
||||
}
|
||||
if cfg.BrandLibrary.MaxQuestionsPerKeyword != 5 {
|
||||
t.Fatalf("expected default max questions per keyword 5, got %d", cfg.BrandLibrary.MaxQuestionsPerKeyword)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTestConfig(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user