- Backend: N8N_AUTH_WEBHOOK из env (fallback), банки из BANK_IP, эндпоинт /api/v1/profile/dadata/address для подсказок адресов (FORMA_DADATA_*). - Config: bank_ip, bank_api_url, forma_dadata_api_key, forma_dadata_secret. - Frontend Profile: DatePicker для даты рождения, ИНН 12 цифр + ссылка на ФНС, валидация email, чекбокс «Совпадает с адресом регистрации», AutoComplete адресов через DaData, Select банков из /api/v1/banks/nspk (bankId/bankName). Подробности в CHANGELOG_PROFILE_VALIDATION.md.
291 lines
13 KiB
Python
291 lines
13 KiB
Python
"""
|
||
Конфигурация приложения
|
||
"""
|
||
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)
|
||
# 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
|
||
|
||
# ============================================
|
||
# 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()
|
||
|
||
|