Files
voidea/app/api/v1/users.py
T

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