Files
aiform_prod/backend/app/config.py
Fedor 4b9665b27f config: N8N_PROJECT_FORM_PODROBNEE_WEBHOOK для деталей дела/проекта из CRM
- config.py: n8n_project_form_podrobnee_webhook (из .env)
- CHANGELOG_MINIAPP.md: описание переменной, эндпоинт пока не добавлен
2026-03-02 15:04:46 +03:00

311 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Конфигурация приложения
"""
import os
import json
from pathlib import Path
from pydantic_settings import BaseSettings
from typing import List, Optional
BASE_DIR = Path(__file__).resolve().parents[2]
ENV_PATH = BASE_DIR / ".env"
# Список CORS, обновляется при изменении .env (чтобы не перезапускать бэкенд)
_cors_origins_live: List[str] = []
_settings_cache: Optional["Settings"] = None
_env_mtime_cache: float = 0
class Settings(BaseSettings):
# ============================================
# APPLICATION
# ============================================
app_name: str = "Ticket Form Intake Platform"
app_env: str = "development"
debug: bool = True
# API
api_v1_prefix: str = "/api/v1"
backend_url: str = "http://localhost:8200"
frontend_url: str = "http://localhost:5175"
# ============================================
# DATABASE (PostgreSQL)
# ============================================
postgres_host: str = "147.45.189.234"
postgres_port: int = 5432
postgres_db: str = "default_db"
postgres_user: str = "gen_user"
postgres_password: str = "2~~9_^kVsU?2\\S"
# ============================================
# MYSQL (для проверки полисов ERV)
# ============================================
mysql_host: str = "localhost"
mysql_port: int = 3306
mysql_db: str = "u2768571_crm_db"
mysql_user: str = "root"
mysql_password: str = ""
# ============================================
# MYSQL CRM (vtiger CRM)
# ============================================
mysql_crm_host: str = "localhost" # В режиме network_mode: host используем localhost # Доступ к хосту из Docker контейнера
mysql_crm_port: int = 3306
mysql_crm_db: str = "ci20465_72new"
mysql_crm_user: str = "ci20465_72new"
mysql_crm_password: str = "EcY979Rn"
@property
def database_url(self) -> str:
"""Формирует URL для подключения к PostgreSQL"""
return f"postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}"
# ============================================
# REDIS (внешний — события, буферы, SMS и т.д.)
# ============================================
redis_host: str = "localhost"
redis_port: int = 6379
redis_password: str = "CRM_Redis_Pass_2025_Secure!"
redis_db: int = 0
redis_prefix: str = "ticket_form:"
# Redis для сессий (локальный в Docker — miniapp_redis; снаружи — localhost:6383 или свой)
redis_session_host: str = "localhost"
redis_session_port: int = 6383
redis_session_password: str = ""
redis_session_db: int = 0
@property
def redis_url(self) -> str:
"""Формирует URL для подключения к Redis (внешний)"""
if self.redis_password:
return f"redis://:{self.redis_password}@{self.redis_host}:{self.redis_port}/{self.redis_db}"
return f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}"
@property
def redis_session_url(self) -> str:
"""URL для локального Redis сессий"""
if self.redis_session_password:
return f"redis://:{self.redis_session_password}@{self.redis_session_host}:{self.redis_session_port}/{self.redis_session_db}"
return f"redis://{self.redis_session_host}:{self.redis_session_port}/{self.redis_session_db}"
# ============================================
# RABBITMQ
# ============================================
rabbitmq_host: str = "185.197.75.249"
rabbitmq_port: int = 5672
rabbitmq_user: str = "admin"
rabbitmq_password: str = "tyejvtej"
rabbitmq_vhost: str = "/"
@property
def rabbitmq_url(self) -> str:
"""Формирует URL для подключения к RabbitMQ"""
return f"amqp://{self.rabbitmq_user}:{self.rabbitmq_password}@{self.rabbitmq_host}:{self.rabbitmq_port}{self.rabbitmq_vhost}"
# ============================================
# S3 STORAGE (Timeweb Cloud Storage)
# ============================================
s3_endpoint: str = "https://s3.timeweb.com"
s3_bucket: str = "erv-platform-files"
s3_access_key: str = "your_access_key_here"
s3_secret_key: str = "your_secret_key_here"
s3_region: str = "ru-1"
# ============================================
# OCR SERVICE
# ============================================
ocr_api_url: str = "http://147.45.146.17:8001"
ocr_api_key: str = ""
# ============================================
# AI SERVICE (OpenRouter)
# ============================================
openrouter_api_key: str = "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"
openrouter_base_url: str = "https://openrouter.ai/api/v1"
openrouter_model: str = "google/gemini-2.0-flash-001"
# ============================================
# FLIGHT APIs
# ============================================
# FlightAware
flightaware_api_key: str = "Puz0cdxAHzAEqMRZwtdeqBUSm9naJfwK"
flightaware_base_url: str = "https://aeroapi.flightaware.com/aeroapi"
# AviationStack (резервный)
aviationstack_api_key: str = ""
aviationstack_base_url: str = "http://api.aviationstack.com/v1"
# ============================================
# NSPK BANKS API (и альтернативный BANK_IP из .env)
# ============================================
nspk_banks_api_url: str = "https://qr.nspk.ru/proxyapp/c2bmembers.json"
bank_ip: str = "http://212.193.27.93/api/payouts/dictionaries/nspk-banks"
bank_api_url: str = "http://212.193.27.93/api/payouts/dictionaries/nspk-banks"
# ============================================
# DADATA (подсказки адресов в форме профиля)
# ============================================
forma_dadata_api_key: str = "" # FORMA_DADATA_API_KEY
forma_dadata_secret: str = "" # FORMA_DADATA_SECRET
# ============================================
# SMS SERVICE (SigmaSMS)
# ============================================
sms_api_url: str = "https://online.sigmasms.ru/api/"
sms_login: str = ""
sms_password: str = ""
sms_token: str = ""
sms_sender: str = "lexpriority"
sms_enabled: bool = True
# ============================================
# VTIGER CRM (PHP Bridge)
# ============================================
crm_webservice_url: str = "http://crm.clientright.ru/webservice.php"
crm_webform_url: str = "https://crm.clientright.ru/modules/Webforms/capture.php"
crm_token: str = ""
# ============================================
# RATE LIMITING
# ============================================
rate_limit_per_minute: int = 60
rate_limit_per_hour: int = 1000
# ============================================
# FILE UPLOAD
# ============================================
max_upload_size_mb: int = 50
allowed_file_extensions: str = "pdf,jpg,jpeg,png,heic,heif,webp"
@property
def allowed_extensions_list(self) -> List[str]:
"""Список разрешенных расширений файлов"""
return [ext.strip() for ext in self.allowed_file_extensions.split(",")]
# ============================================
# CORS
# ============================================
cors_origins: str = "http://localhost:5175,http://127.0.0.1:5175,http://147.45.146.17:5175"
@property
def cors_origins_list(self) -> List[str]:
"""Список CORS origins"""
if isinstance(self.cors_origins, str):
return [origin.strip() for origin in self.cors_origins.split(",")]
return self.cors_origins
# ============================================
# N8N API & WEBHOOKS
# ============================================
n8n_url: str = "https://n8n.clientright.pro"
n8n_api_key: str = "" # Нужно задать в .env
n8n_policy_check_webhook: str = ""
n8n_file_upload_webhook: str = ""
n8n_create_contact_webhook: str = "https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27"
n8n_create_claim_webhook: str = "https://n8n.clientright.pro/webhook/d5bf4ca6-9e44-44b9-9714-3186ea703e7d"
n8n_description_webhook: str = "https://n8n.clientright.ru/webhook/ticket_form_description" # Webhook для описания проблемы (переопределяется через N8N_DESCRIPTION_WEBHOOK в .env)
# Консультации: тикеты из CRM (MySQL) — тот же payload, что и у других хуков
n8n_ticket_form_consultation_webhook: str = "" # N8N_TICKET_FORM_CONSULTATION_WEBHOOK в .env
# Подробнее по тикету: session + ticket_id → ответ вебхука (HTML/JSON)
n8n_ticket_form_podrobnee_webhook: str = "" # N8N_TICKET_FORM_PODROBNEE_WEBHOOK в .env
n8n_project_form_podrobnee_webhook: str = "" # N8N_PROJECT_FORM_PODROBNEE_WEBHOOK — детали дела/проекта из CRM по project_id
# Wizard и финальная отправка заявки (create) — один webhook, меняется через .env
n8n_ticket_form_final_webhook: str = "https://n8n.clientright.pro/webhook/ecc93306-fadc-489a-afdb-d3e981013df3"
n8n_tg_auth_webhook: str = "" # Webhook для авторизации пользователей Telegram WebApp (Mini App)
# Контактные данные из CRM для раздела «Профиль» (массив или пусто)
n8n_contact_webhook: str = "" # N8N_CONTACT_WEBHOOK в .env
n8n_profile_update_webhook: str = "" # N8N_PROFILE_UPDATE_WEBHOOK в .env — обновление профиля (verification=0)
# ============================================
# TELEGRAM BOT
# ============================================
telegram_bot_token: str = "" # Токен бота для проверки initData WebApp
def get_telegram_bot_tokens(self) -> List[tuple]:
"""Список (bot_id, token) для проверки подписи Telegram initData. Один токен — [('default', token)]."""
token = (self.telegram_bot_token or "").strip()
if token:
return [("default", token)]
return []
# ============================================
# MAX (мессенджер) — Mini App auth
# ============================================
max_bot_token: str = "" # Токен бота MAX (один бот)
max_bot_tokens: str = "" # Мультибот: JSON {"bot_id": "token", ...}. Если задан — используется вместо max_bot_token.
def get_max_bot_tokens(self) -> List[tuple]:
"""Список (bot_id, token) для проверки подписи MAX initData. Из MAX_BOT_TOKENS (JSON) или [('default', MAX_BOT_TOKEN)]."""
s = (self.max_bot_tokens or os.environ.get("MAX_BOT_TOKENS") or "").strip()
if s:
try:
d = json.loads(s)
out = [(k, str(v).strip()) for k, v in d.items() if v and str(v).strip()]
if out:
return out
except Exception:
pass
token = (self.max_bot_token or os.environ.get("MAX_BOT_TOKEN") or "").strip()
if token:
return [("default", token)]
return []
n8n_max_auth_webhook: str = "" # Webhook n8n: max_user_id → unified_id, contact_id, has_drafts
n8n_auth_webhook: str = "" # Универсальный auth: channel + channel_user_id + init_data → unified_id, phone, contact_id, has_drafts
# ============================================
# ПОДДЕРЖКА (чат, треды, n8n webhook)
# ============================================
n8n_support_webhook: str = "" # N8N_SUPPORT_WEBHOOK — URL webhook n8n (multipart). Обязателен для отправки сообщений.
support_attachments_max_count: int = 0 # 0 = без ограничений
support_attachments_max_size_mb: int = 0 # 0 = без ограничений
support_attachments_allowed_types: str = "" # пусто = любые (например: .pdf,.jpg,image/*)
support_incoming_secret: str = "" # Секрет для POST /api/v1/support/incoming (n8n → backend)
@property
def support_attachments_max_size_bytes(self) -> int:
if self.support_attachments_max_size_mb <= 0:
return 0
return self.support_attachments_max_size_mb * 1024 * 1024
# ============================================
# LOGGING
# ============================================
log_level: str = "INFO"
log_file: str = "/app/logs/ticket_form_backend.log"
class Config:
env_file = str(ENV_PATH)
case_sensitive = False
extra = "ignore" # Игнорируем лишние поля из .env
def get_settings() -> Settings:
"""Текущие настройки. При изменении .env подхватываются без перезапуска."""
global _settings_cache, _env_mtime_cache, _cors_origins_live
mtime = os.path.getmtime(ENV_PATH) if ENV_PATH.exists() else 0.0
if _settings_cache is None or mtime > _env_mtime_cache:
_settings_cache = Settings()
_env_mtime_cache = mtime
_cors_origins_live.clear()
_cors_origins_live.extend(_settings_cache.cors_origins_list)
return _settings_cache
def get_cors_origins_live() -> List[str]:
"""
Список CORS origins для middleware; обновляется при изменении .env без перезапуска.
Обработчики, которые используют get_settings() при каждом запросе, тоже видят новые значения.
"""
get_settings() # обновить кеш и _cors_origins_live при изменении .env
return _cors_origins_live
settings = get_settings()