Portal Community

Enabling Redis for a Tenant

Default cache is in-memory. To switch a tenant to Redis:

appsettings.Tenant.apexretail.json — switch the cache type (committed):

{
  "SharpCache": {
    "CacheType": "Redis"
  }
}

appsettings.Tenant.apexretail.production.json — production Redis address (not committed):

{
  "BizFirstAIDatabase": {
    "Redis": "redis.apexretail.local:6379,password=<vault>,ssl=false,abortConnect=false,connectTimeout=5000"
  },
  "Database": {
    "Redis": "redis.apexretail.local:6379,password=<vault>,ssl=false,abortConnect=false,connectTimeout=5000"
  }
}

appsettings.Tenant.apexretail.development.json — local Docker Redis (committed):

{
  "BizFirstAIDatabase": {
    "Redis": "localhost:6379,abortConnect=false"
  },
  "Database": {
    "Redis": "localhost:6379,abortConnect=false"
  }
}

Enabling Kafka for a Tenant

Kafka is disabled by default. Topic names are stable across envs; broker addresses differ.

appsettings.Tenant.apexretail.json — enable and name topics (committed):

{
  "EdgeStream": {
    "Kafka": {
      "Enabled": true,
      "EventTopicName": "apexretail.events",
      "DeadLetterTopicName": "apexretail.events.dlq",
      "ConsumerGroupId": "apexretail-signalr",
      "SeparateChatTopic": true,
      "ChatTopicName": "apexretail.chat",
      "TopicPartitionCount": 12,
      "TopicReplicationFactor": 3,
      "Acks": "all",
      "EnableIdempotence": true,
      "CompressionType": "snappy"
    }
  }
}

appsettings.Tenant.apexretail.production.json — broker addresses with SASL/SSL (not committed):

{
  "EdgeStream": {
    "Kafka": {
      "BootstrapServers": "kafka1.apexretail.local:9092,kafka2.apexretail.local:9092,kafka3.apexretail.local:9092",
      "SecurityProtocol": "sasl_ssl",
      "SaslMechanism": "PLAIN",
      "SaslUsername": "bizfirst-svc",
      "SaslPassword": "<vault>",
      "SslCaLocation": "/etc/ssl/certs/apexretail-ca.crt"
    }
  }
}

appsettings.Tenant.apexretail.development.json — local Kafka (committed):

{
  "EdgeStream": {
    "Kafka": {
      "BootstrapServers": "localhost:9092",
      "SecurityProtocol": "plaintext"
    }
  }
}

Tenant-Specific Observability

Each tenant can send traces and logs to their own Grafana/Loki/OTEL stack.

appsettings.Tenant.apexretail.production.json:

{
  "BizFirst": {
    "Observability": {
      "ServiceName": "BizFirst.Ai.ApexRetail",
      "Environment": "production",
      "Tracing": {
        "Enabled": true,
        "OtlpEndpoint": "http://otel.apexretail.local:4317",
        "SamplingRate": 0.1
      },
      "Logging": {
        "Loki": {
          "Enabled": true,
          "Url": "http://loki.apexretail.local:3100",
          "MinimumLevel": "Warning"
        }
      },
      "Grafana": {
        "Enabled": true,
        "Url": "https://grafana.apexretail.local",
        "ApiKey": "<vault>"
      },
      "DeepHealthChecks": {
        "SqlServerConnectionString": "Data Source=sql.apexretail.local;...",
        "RedisConnectionString": "redis.apexretail.local:6379,password=<vault>"
      }
    }
  }
}

appsettings.Tenant.apexretail.development.json — verbose tracing, Loki off:

{
  "BizFirst": {
    "Observability": {
      "ServiceName": "BizFirst.Ai.ApexRetail.Dev",
      "Environment": "development",
      "Tracing": {
        "SamplingRate": 1.0
      },
      "Logging": {
        "Loki": { "Enabled": false }
      }
    }
  }
}

Tenant LLM Model Selection

Base defaults use gpt-4o-mini. A tenant on a premium plan gets gpt-4o with their own API key.

appsettings.Tenant.apexretail.json — model preference (committed):

{
  "Agent": {
    "LlmConfig": {
      "Provider": "openai",
      "Model": "gpt-4o"
    }
  }
}

appsettings.Tenant.apexretail.production.json — API key (not committed):

{
  "LlmProviders": [
    {
      "Provider": "openai",
      "Models": [
        {
          "Id": "gpt-4",
          "Name": "gpt-4o",
          "Version": "2024-11-20",
          "ApiKey": "<vault>",
          "Type": "chat",
          "MultiModal": true
        }
      ]
    }
  ]
}

Tenant JWT Configuration

Each tenant can have its own issuer, audience, and token lifetime.

appsettings.Tenant.apexretail.production.json:

{
  "JWT": {
    "Key": "<32-byte-base64-from-vault>",
    "Issuer": "BizFirst.ApexRetail",
    "Audience": "ApexRetail.Clients",
    "ExpiryMinutes": 480
  }
}

Tenant CORS Origins

The base appsettings.json lists localhost ports for development. A tenant overrides this with their real portal URLs.

appsettings.Tenant.apexretail.json:

{
  "CorsSettings": {
    "AllowedOrigins": [
      "https://portal.apexretail.com",
      "https://admin.apexretail.com",
      "https://pos.apexretail.com"
    ]
  }
}

Decision Table — What Goes Where

Setting typeFileCommitted?
Feature flags, topic names, model choiceTenant.{code}.jsonYes
Shared dev DB addressTenant.{code}.development.jsonYes
Production DB, Redis, Kafka addressesTenant.{code}.production.jsonNo
Passwords, API keys, JWT signing keysTenant.{code}.production.json (from vault)No
Your local SQL Server nameappsettings.Development.Local.jsonNo
Local Kafka / Redis (Docker)appsettings.Development.Local.jsonNo