Files

190 lines
5.8 KiB
Python

"""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,
)