"""Users API routes for VoIdea.""" from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.dependencies import get_current_user, get_db from app.models.user import User from app.schemas.user import SubscriptionInfo, UserResponse, UserUpdate, VoiceSettingsResponse, VoiceSettingsUpdate from app.services.tariff_service import TariffService from app.services.user_service import UserService router = APIRouter() def _user_to_response(user: User) -> UserResponse: return UserResponse( id=str(user.id), email=user.email, display_name=user.display_name, avatar_url=user.avatar_url, is_active=user.is_active, is_superuser=user.is_superuser, oauth_provider=user.oauth_provider, created_at=user.created_at, updated_at=user.updated_at, ) @router.get("/me", response_model=UserResponse) async def get_me(user: Annotated[User, Depends(get_current_user)]): return _user_to_response(user) @router.patch("/me", response_model=UserResponse) async def update_me( body: UserUpdate, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): service = UserService(db) result = await service.update_profile( str(user.id), display_name=body.display_name, avatar_url=body.avatar_url, ) if not result: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") return _user_to_response(result) @router.delete("/me", status_code=status.HTTP_204_NO_CONTENT) async def delete_me( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): service = UserService(db) await service.delete(str(user.id)) VOICE_PRESETS: dict[str, dict[str, Any]] = { "standard": { "vad_noise_threshold": 0.3, "vad_silence_timeout_ms": 1500, "confidence_verified": 80, "confidence_warning": 50, "semantic_mode": "fast", "tts_enabled": True, "continuous_listening": False, }, "precise": { "vad_noise_threshold": 0.2, "vad_silence_timeout_ms": 2000, "confidence_verified": 85, "confidence_warning": 60, "semantic_mode": "full", "tts_enabled": True, "continuous_listening": False, }, "fast": { "vad_noise_threshold": 0.4, "vad_silence_timeout_ms": 1000, "confidence_verified": 70, "confidence_warning": 40, "semantic_mode": "fast", "tts_enabled": False, "continuous_listening": True, }, "quiet": { "vad_noise_threshold": 0.6, "vad_silence_timeout_ms": 3000, "confidence_verified": 80, "confidence_warning": 50, "semantic_mode": "fast", "tts_enabled": True, "continuous_listening": False, }, "expert": { "vad_noise_threshold": 0.15, "vad_silence_timeout_ms": 1000, "confidence_verified": 90, "confidence_warning": 70, "semantic_mode": "full", "tts_enabled": True, "continuous_listening": True, }, } @router.get("/me/voice-settings", response_model=VoiceSettingsResponse) async def get_voice_settings( user: Annotated[User, Depends(get_current_user)], ): tuning = user.pipeline_tuning or {} preset = tuning.get("preset", "standard") overrides = tuning.get("overrides", {}) return VoiceSettingsResponse( preset=preset, overrides=overrides, available_presets=list(VOICE_PRESETS.keys()), preset_values=VOICE_PRESETS.get(preset, VOICE_PRESETS["standard"]), ) @router.patch("/me/voice-settings", response_model=VoiceSettingsResponse) async def update_voice_settings( body: VoiceSettingsUpdate, user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): from sqlalchemy import select tuning = user.pipeline_tuning or {} if body.reset: tuning = {"preset": "standard", "overrides": {}} else: if body.preset is not None: if body.preset not in VOICE_PRESETS: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unknown preset: {body.preset}. Available: {', '.join(VOICE_PRESETS.keys())}", ) tuning["preset"] = body.preset if body.overrides is not None: existing_overrides = tuning.get("overrides", {}) existing_overrides.update(body.overrides) tuning["overrides"] = existing_overrides result = await db.execute(select(User).where(User.id == user.id)) db_user = result.scalar_one() db_user.pipeline_tuning = tuning await db.commit() preset = tuning.get("preset", "standard") preset_vals = VOICE_PRESETS.get(preset, VOICE_PRESETS["standard"]) overrides = tuning.get("overrides", {}) merged = {**preset_vals, **overrides} return VoiceSettingsResponse( preset=preset, overrides=overrides, available_presets=list(VOICE_PRESETS.keys()), preset_values=merged, ) @router.get("/me/subscription", response_model=SubscriptionInfo) async def get_my_subscription( user: Annotated[User, Depends(get_current_user)], db: AsyncSession = Depends(get_db), ): svc = TariffService(db) sub = await svc.get_user_subscription(user.id) if not sub: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subscription not found" ) plan = await svc.get_plan_by_id(sub.plan_id) return SubscriptionInfo( plan_name=plan.name if plan else "Бесплатно", plan_code=plan.code if plan else "free", status=sub.status, expires_at=sub.current_period_end, features=plan.features if plan else None, )