Initial commit: VoIdeaAI - voice-first AI idea assistant

This commit is contained in:
2026-05-13 12:51:42 +03:00
commit 688d043dad
421 changed files with 47915 additions and 0 deletions
+204
View File
@@ -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()