- Migration 006: adds role TEXT+CHECK column to portal_users, backfills is_admin -> platform_admin/customer_admin, drops is_admin - Migration 006: creates user_tenant_roles table (UNIQUE user_id+tenant_id) - Migration 006: creates portal_invitations table with token_hash, status, expires_at - PortalUser: replaced is_admin (bool) with role (str, default customer_admin) - Added UserRole enum (PLATFORM_ADMIN, CUSTOMER_ADMIN, CUSTOMER_OPERATOR) - Added UserTenantRole ORM model with FK cascade deletes - Added PortalInvitation ORM model with token_hash unique constraint - Settings: added invite_secret, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from_email fields
226 lines
8.0 KiB
Python
226 lines
8.0 KiB
Python
"""
|
|
Konstruct shared configuration.
|
|
|
|
Loads all environment variables via Pydantic Settings with sensible defaults
|
|
for local development. All services import from this module.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Application settings loaded from environment variables."""
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
case_sensitive=False,
|
|
extra="ignore",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Database
|
|
# -------------------------------------------------------------------------
|
|
database_url: str = Field(
|
|
default="postgresql+asyncpg://konstruct_app:konstruct_dev@localhost:5432/konstruct",
|
|
description="Async database URL — must use konstruct_app role, not superuser",
|
|
)
|
|
database_admin_url: str = Field(
|
|
default="postgresql+asyncpg://postgres:postgres_dev@localhost:5432/konstruct",
|
|
description="Admin database URL for Alembic migrations (superuser)",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Redis
|
|
# -------------------------------------------------------------------------
|
|
redis_url: str = Field(
|
|
default="redis://localhost:6379/0",
|
|
description="Redis connection URL",
|
|
)
|
|
celery_broker_url: str = Field(
|
|
default="redis://localhost:6379/1",
|
|
description="Celery broker URL",
|
|
)
|
|
celery_result_backend: str = Field(
|
|
default="redis://localhost:6379/2",
|
|
description="Celery result backend URL",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Slack
|
|
# -------------------------------------------------------------------------
|
|
slack_bot_token: str = Field(
|
|
default="",
|
|
description="Slack bot token (xoxb-...)",
|
|
)
|
|
slack_signing_secret: str = Field(
|
|
default="",
|
|
description="Slack signing secret for webhook verification",
|
|
)
|
|
slack_app_token: str = Field(
|
|
default="",
|
|
description="Slack app-level token for Socket Mode (xapp-...)",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# WhatsApp
|
|
# -------------------------------------------------------------------------
|
|
whatsapp_app_secret: str = Field(
|
|
default="",
|
|
description="WhatsApp app secret for HMAC-SHA256 webhook signature verification",
|
|
)
|
|
whatsapp_verify_token: str = Field(
|
|
default="",
|
|
description="WhatsApp webhook verification token (hub.verify_token)",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# MinIO / Object Storage
|
|
# -------------------------------------------------------------------------
|
|
minio_endpoint: str = Field(
|
|
default="http://localhost:9000",
|
|
description="MinIO endpoint URL (S3-compatible)",
|
|
)
|
|
minio_access_key: str = Field(
|
|
default="minioadmin",
|
|
description="MinIO access key",
|
|
)
|
|
minio_secret_key: str = Field(
|
|
default="minioadmin",
|
|
description="MinIO secret key",
|
|
)
|
|
minio_media_bucket: str = Field(
|
|
default="konstruct-media",
|
|
description="MinIO bucket name for media attachments",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# LLM Providers
|
|
# -------------------------------------------------------------------------
|
|
anthropic_api_key: str = Field(
|
|
default="",
|
|
description="Anthropic API key",
|
|
)
|
|
openai_api_key: str = Field(
|
|
default="",
|
|
description="OpenAI API key",
|
|
)
|
|
ollama_base_url: str = Field(
|
|
default="http://localhost:11434",
|
|
description="Ollama inference server base URL",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Auth / Security
|
|
# -------------------------------------------------------------------------
|
|
auth_secret: str = Field(
|
|
default="insecure-dev-secret-change-in-production",
|
|
description="Secret key for signing JWT tokens",
|
|
)
|
|
invite_secret: str = Field(
|
|
default="insecure-invite-secret-change-in-production",
|
|
description="HMAC secret for signing invite tokens (separate from auth_secret)",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SMTP (for invitation emails)
|
|
# -------------------------------------------------------------------------
|
|
smtp_host: str = Field(
|
|
default="localhost",
|
|
description="SMTP server hostname",
|
|
)
|
|
smtp_port: int = Field(
|
|
default=587,
|
|
description="SMTP server port",
|
|
)
|
|
smtp_username: str = Field(
|
|
default="",
|
|
description="SMTP authentication username",
|
|
)
|
|
smtp_password: str = Field(
|
|
default="",
|
|
description="SMTP authentication password",
|
|
)
|
|
smtp_from_email: str = Field(
|
|
default="noreply@konstruct.dev",
|
|
description="From address for outbound emails",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Service URLs
|
|
# -------------------------------------------------------------------------
|
|
gateway_url: str = Field(default="http://localhost:8001")
|
|
router_url: str = Field(default="http://localhost:8002")
|
|
orchestrator_url: str = Field(default="http://localhost:8003")
|
|
llm_pool_url: str = Field(default="http://localhost:8004")
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Encryption
|
|
# -------------------------------------------------------------------------
|
|
platform_encryption_key: str = Field(
|
|
default="",
|
|
description="Fernet key for BYO API key encryption (base64-encoded 32-byte key)",
|
|
)
|
|
platform_encryption_key_previous: str = Field(
|
|
default="",
|
|
description="Previous Fernet key retained for decryption during rotation window",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Stripe
|
|
# -------------------------------------------------------------------------
|
|
stripe_secret_key: str = Field(
|
|
default="",
|
|
description="Stripe secret API key (sk_live_... or sk_test_...)",
|
|
)
|
|
stripe_webhook_secret: str = Field(
|
|
default="",
|
|
description="Stripe webhook endpoint signing secret (whsec_...)",
|
|
)
|
|
stripe_per_agent_price_id: str = Field(
|
|
default="",
|
|
description="Stripe Price ID for the per-agent monthly subscription plan",
|
|
)
|
|
portal_url: str = Field(
|
|
default="http://localhost:3000",
|
|
description="Portal base URL used in Stripe checkout success/cancel redirects",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Slack OAuth
|
|
# -------------------------------------------------------------------------
|
|
slack_client_id: str = Field(
|
|
default="",
|
|
description="Slack OAuth app client ID",
|
|
)
|
|
slack_client_secret: str = Field(
|
|
default="",
|
|
description="Slack OAuth app client secret",
|
|
)
|
|
slack_oauth_redirect_uri: str = Field(
|
|
default="http://localhost:3000/api/slack/callback",
|
|
description="Slack OAuth redirect URI (must match Slack app config)",
|
|
)
|
|
oauth_state_secret: str = Field(
|
|
default="",
|
|
description="HMAC secret for signing OAuth state parameters (CSRF protection)",
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Application
|
|
# -------------------------------------------------------------------------
|
|
environment: str = Field(default="development")
|
|
log_level: str = Field(default="INFO")
|
|
debug: bool = Field(default=False)
|
|
default_rate_limit_rpm: int = Field(
|
|
default=60,
|
|
description="Default requests per minute per tenant",
|
|
)
|
|
|
|
|
|
# Module-level singleton — imported by all services
|
|
settings = Settings()
|