Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
def _read_version() -> str:
|
||||
version_file = Path(__file__).resolve().parent.parent.parent / "VERSION"
|
||||
try:
|
||||
return version_file.read_text(encoding="utf-8").strip()
|
||||
except (FileNotFoundError, OSError):
|
||||
return "1.0.0"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# Project
|
||||
project_name: str = Field(default="VoIdeaAI", description="Project name")
|
||||
project_version: str = Field(default_factory=_read_version, description="Project version (from VERSION file)")
|
||||
project_env: str = Field(default="local", description="Environment")
|
||||
project_owner: str = Field(default="Owner", description="Project owner name")
|
||||
project_license: str = Field(default="AGPL-3.0", description="License type")
|
||||
|
||||
# Server
|
||||
server_host: str = Field(default="0.0.0.0", description="Server host")
|
||||
server_port: int = Field(default=8020, description="Server port")
|
||||
server_external_url: str = Field(default="http://localhost:8020", description="External URL")
|
||||
|
||||
# Database
|
||||
database_url: str = Field(
|
||||
default="postgresql+asyncpg://voidea:password@localhost:5432/voidea",
|
||||
description="Database URL (async, PostgreSQL with asyncpg)",
|
||||
)
|
||||
db_echo: bool = Field(default=False, description="Echo SQL queries")
|
||||
|
||||
@property
|
||||
def sync_database_url(self) -> str:
|
||||
return self.database_url.replace("+asyncpg", "")
|
||||
|
||||
# Redis
|
||||
redis_host: str = Field(default="localhost", description="Redis host")
|
||||
redis_port: int = Field(default=6379, description="Redis port")
|
||||
redis_url: str = Field(default="redis://localhost:6379/0", description="Redis URL")
|
||||
|
||||
# JWT
|
||||
jwt_secret_key: str = Field(default="", description="JWT secret key")
|
||||
jwt_reset_secret_key: str = Field(default="", description="JWT secret for password reset tokens (separate from access)")
|
||||
jwt_algorithm: str = Field(default="HS256", description="JWT algorithm")
|
||||
jwt_access_token_expire_minutes: int = Field(default=60, description="Access token TTL")
|
||||
jwt_refresh_token_expire_days: int = Field(default=30, description="Refresh token TTL")
|
||||
|
||||
# AI Providers
|
||||
openai_api_key: str = Field(default="", description="OpenAI API key (Whisper, GPT)")
|
||||
ai_yandex_key: str = Field(default="", description="Yandex GPT API key or IAM token")
|
||||
ai_yandex_url: str = Field(default="https://llm.api.cloud.yandex.net", description="Yandex API URL")
|
||||
ai_yandex_folder_id: str = Field(default="", description="Yandex Cloud folder ID")
|
||||
ai_gigachat_client_id: str = Field(default="", description="GigaChat OAuth client ID")
|
||||
ai_gigachat_secret: str = Field(default="", description="GigaChat OAuth client secret")
|
||||
ai_gigachat_url: str = Field(default="https://gigachat.devices.sber.ru", description="GigaChat API URL")
|
||||
ai_fallback_model: str = Field(default="yandex_gpt", description="Default AI model")
|
||||
ai_timeout: int = Field(default=10, description="AI request timeout")
|
||||
ai_max_retries: int = Field(default=3, description="Max retries for AI requests")
|
||||
|
||||
# OAuth
|
||||
oauth_yandex_id: str = Field(default="", description="Yandex OAuth client ID")
|
||||
oauth_yandex_secret: str = Field(default="", description="Yandex OAuth client secret")
|
||||
oauth_yandex_redirect_uri: str = Field(default="http://localhost:3000/oauth/callback")
|
||||
oauth_google_id: str = Field(default="", description="Google OAuth client ID")
|
||||
oauth_google_secret: str = Field(default="", description="Google OAuth client secret")
|
||||
oauth_google_redirect_uri: str = Field(default="http://localhost:8020/auth/google/callback")
|
||||
oauth_apple_id: str = Field(default="", description="Apple OAuth client ID")
|
||||
oauth_apple_secret: str = Field(default="", description="Apple OAuth client secret")
|
||||
oauth_apple_redirect_uri: str = Field(default="http://localhost:8020/auth/apple/callback")
|
||||
|
||||
@property
|
||||
def google_oauth_enabled(self) -> bool:
|
||||
return bool(self.oauth_google_id)
|
||||
|
||||
@property
|
||||
def apple_oauth_enabled(self) -> bool:
|
||||
return bool(self.oauth_apple_id)
|
||||
|
||||
# Telegram Bot
|
||||
telegram_bot_token: str = Field(default="", description="Telegram bot token")
|
||||
|
||||
# Email (SMTP)
|
||||
smtp_host: str = Field(default="", description="SMTP server host")
|
||||
smtp_port: int = Field(default=587, description="SMTP server port")
|
||||
smtp_user: str = Field(default="", description="SMTP username")
|
||||
smtp_pass: str = Field(default="", description="SMTP password")
|
||||
smtp_from: str = Field(default="VoIdea <notifications@voidea.ru>")
|
||||
smtp_tls: bool = Field(default=True)
|
||||
|
||||
# Security
|
||||
enable_2fa: bool = Field(default=False)
|
||||
accepted_terms_version: str = Field(
|
||||
default="2026-05-11",
|
||||
description="Current version of Terms of Service / Privacy Policy",
|
||||
)
|
||||
|
||||
# System Owner
|
||||
system_owner_email: str = Field(
|
||||
default="",
|
||||
description="Email of the system owner (set on VPS deploy, protected from deletion/suspension)",
|
||||
)
|
||||
|
||||
# Social Networks (empty = hidden)
|
||||
social_telegram: str = Field(default="voideaai", description="Telegram account name")
|
||||
social_vk: str = Field(default="voideaai", description="VK account name")
|
||||
social_youtube: str = Field(default="voideaai", description="YouTube account name")
|
||||
social_tiktok: str = Field(default="voideaai", description="TikTok account name")
|
||||
project_slogan: str = Field(
|
||||
default="VoIdeaAI — идеи рождаются вслух, решения приходят мгновенно!",
|
||||
description="Project slogan",
|
||||
)
|
||||
|
||||
# Analytics (empty = disabled)
|
||||
yandex_metrika_id: str = Field(default="", description="Yandex Metrika counter ID")
|
||||
google_analytics_id: str = Field(default="", description="Google Analytics tracking ID")
|
||||
|
||||
# Tariffs
|
||||
tariffs_enabled: bool = Field(default=False, description="Enable tariff system (false = promo free-for-all)")
|
||||
tariffs_free_code: str = Field(default="free", description="Code of the default free tariff plan")
|
||||
|
||||
# Push Notifications (mobile-ready stubs)
|
||||
fcm_server_key: str = Field(default="", description="Firebase Cloud Messaging server key (mobile push)")
|
||||
apns_key_id: str = Field(default="", description="Apple Push Notification Service key ID (iOS push)")
|
||||
|
||||
# Logging
|
||||
log_level: str = Field(default="INFO")
|
||||
log_format: str = Field(default="json")
|
||||
log_file_path: str = Field(default="logs/app.log")
|
||||
log_max_bytes: int = Field(default=10485760)
|
||||
log_backup_count: int = Field(default=5)
|
||||
|
||||
# CORS
|
||||
cors_origins: str = Field(default="http://localhost:3000,http://localhost:8020")
|
||||
|
||||
@property
|
||||
def cors_origins_list(self) -> list[str]:
|
||||
return [origin.strip() for origin in self.cors_origins.split(",")]
|
||||
|
||||
# Celery
|
||||
celery_broker_url: str = Field(default="redis://localhost:6379/0")
|
||||
celery_result_backend: str = Field(default="redis://localhost:6379/0")
|
||||
celery_task_track_started: bool = Field(default=True)
|
||||
celery_task_time_limit: int = Field(default=300)
|
||||
|
||||
# Observer Agent
|
||||
observer_enabled: bool = Field(default=True)
|
||||
observer_sample_rate: float = Field(default=0.1)
|
||||
observer_store_raw_data: bool = Field(default=False)
|
||||
|
||||
# Rate Limiting
|
||||
rate_limit_enabled: bool = Field(default=True, description="Enable rate limiting")
|
||||
rate_limit_default: str = Field(default="60/minute", description="Default rate limit")
|
||||
rate_limit_auth: str = Field(default="10/minute", description="Auth endpoints rate limit")
|
||||
|
||||
# Crypto
|
||||
encryption_key: str = Field(default="", description="AES-256 encryption key (Fernet)")
|
||||
|
||||
# Development
|
||||
debug: bool = Field(default=False)
|
||||
reload: bool = Field(default=True)
|
||||
|
||||
def is_production(self) -> bool:
|
||||
return self.project_env == "production"
|
||||
|
||||
def is_development(self) -> bool:
|
||||
return self.project_env in ("development", "local")
|
||||
|
||||
@property
|
||||
def public_config(self) -> dict:
|
||||
"""Non-sensitive settings exposed via GET /api/v1/config/public."""
|
||||
return {
|
||||
"project_name": self.project_name,
|
||||
"project_version": self.project_version,
|
||||
"project_env": self.project_env,
|
||||
"project_slogan": self.project_slogan,
|
||||
"social_telegram": self.social_telegram,
|
||||
"social_vk": self.social_vk,
|
||||
"social_youtube": self.social_youtube,
|
||||
"social_tiktok": self.social_tiktok,
|
||||
"yandex_metrika_id": self.yandex_metrika_id,
|
||||
"google_analytics_id": self.google_analytics_id,
|
||||
"tariffs_enabled": self.tariffs_enabled,
|
||||
"tariffs_free_code": self.tariffs_free_code,
|
||||
"accepted_terms_version": self.accepted_terms_version,
|
||||
}
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
Reference in New Issue
Block a user