2026-09-08 15:42:28 +00:00
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Any, Literal
|
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, EmailStr, Field, field_validator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegisterRequest(BaseModel):
|
|
|
|
|
email: EmailStr
|
|
|
|
|
password: str = Field(min_length=8, max_length=128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
|
|
|
email: EmailStr
|
|
|
|
|
password: str = Field(min_length=1, max_length=128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PasswordChangeRequest(BaseModel):
|
|
|
|
|
current_password: str = Field(min_length=1, max_length=128)
|
|
|
|
|
new_password: str = Field(min_length=8, max_length=128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserPublic(BaseModel):
|
|
|
|
|
id: UUID
|
|
|
|
|
email: str | None
|
|
|
|
|
phone: str | None
|
|
|
|
|
role: Literal["user", "admin"]
|
|
|
|
|
plan: Literal["free", "vip"]
|
|
|
|
|
status: Literal["active", "disabled"]
|
|
|
|
|
email_verified: bool
|
|
|
|
|
phone_verified: bool
|
|
|
|
|
created_at: datetime
|
|
|
|
|
last_login_at: datetime | None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthResponse(BaseModel):
|
|
|
|
|
user: UserPublic
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MembershipRequest(BaseModel):
|
|
|
|
|
plan: Literal["free", "vip"]
|
|
|
|
|
starts_at: datetime | None = None
|
|
|
|
|
expires_at: datetime | None = None
|
|
|
|
|
reason: str = Field(min_length=1, max_length=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StatusRequest(BaseModel):
|
|
|
|
|
status: Literal["active", "disabled"]
|
|
|
|
|
reason: str = Field(min_length=1, max_length=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class QuotaAdjustmentRequest(BaseModel):
|
|
|
|
|
amount: int
|
|
|
|
|
reason: str = Field(min_length=1, max_length=500)
|
|
|
|
|
idempotency_key: str = Field(min_length=8, max_length=120)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VerificationConfirmRequest(BaseModel):
|
|
|
|
|
challenge_id: UUID
|
|
|
|
|
code: str = Field(min_length=4, max_length=12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VerificationSendRequest(BaseModel):
|
|
|
|
|
channel: Literal["email", "phone"]
|
|
|
|
|
purpose: Literal["registration", "contact_binding", "contact_change", "password_reset"]
|
|
|
|
|
|
|
|
|
|
|
2026-09-08 16:42:13 +00:00
|
|
|
class TtsTaskRequest(BaseModel):
|
|
|
|
|
text: str = Field(min_length=1, max_length=10000)
|
|
|
|
|
voice_id: str = Field(min_length=1, max_length=120)
|
|
|
|
|
parameters: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TtsVoicePublic(BaseModel):
|
|
|
|
|
id: UUID
|
|
|
|
|
provider_voice_id: str
|
|
|
|
|
name: str
|
|
|
|
|
language: str | None
|
|
|
|
|
description: str | None
|
|
|
|
|
supported_parameters: dict[str, Any]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TtsTaskPublic(BaseModel):
|
|
|
|
|
id: UUID
|
|
|
|
|
status: str
|
|
|
|
|
text_length: int
|
|
|
|
|
voice_id: str
|
|
|
|
|
parameters: dict[str, Any]
|
|
|
|
|
error_code: str | None = None
|
|
|
|
|
audio_available: bool = False
|
|
|
|
|
audio_expires_at: datetime | None = None
|
|
|
|
|
created_at: datetime
|
|
|
|
|
started_at: datetime | None = None
|
|
|
|
|
finished_at: datetime | None = None
|
|
|
|
|
|
|
|
|
|
|
2026-09-08 15:42:28 +00:00
|
|
|
def normalize_email(value: str) -> str:
|
|
|
|
|
return value.strip().lower()
|