mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-03 18:50:24 +00:00
192 lines
4.7 KiB
Python
192 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
|
|
|
|
class PollOptionCreate(BaseModel):
|
|
text: str = Field(..., min_length=1, max_length=500)
|
|
|
|
@field_validator("text")
|
|
@classmethod
|
|
def strip_text(cls, value: str) -> str:
|
|
text = value.strip()
|
|
if not text:
|
|
raise ValueError("Option text cannot be empty")
|
|
return text
|
|
|
|
|
|
class PollQuestionCreate(BaseModel):
|
|
text: str = Field(..., min_length=1, max_length=1000)
|
|
options: list[PollOptionCreate] = Field(..., min_length=2)
|
|
|
|
@field_validator("text")
|
|
@classmethod
|
|
def strip_question_text(cls, value: str) -> str:
|
|
text = value.strip()
|
|
if not text:
|
|
raise ValueError("Question text cannot be empty")
|
|
return text
|
|
|
|
@field_validator("options")
|
|
@classmethod
|
|
def validate_options(cls, value: list[PollOptionCreate]) -> list[PollOptionCreate]:
|
|
seen: set[str] = set()
|
|
for option in value:
|
|
normalized = option.text.lower()
|
|
if normalized in seen:
|
|
raise ValueError("Option texts must be unique within a question")
|
|
seen.add(normalized)
|
|
return value
|
|
|
|
|
|
class PollCreateRequest(BaseModel):
|
|
title: str = Field(..., min_length=1, max_length=255)
|
|
description: Optional[str] = Field(default=None, max_length=4000)
|
|
reward_enabled: bool = False
|
|
reward_amount_kopeks: int = Field(default=0, ge=0, le=1_000_000_000)
|
|
questions: list[PollQuestionCreate] = Field(..., min_length=1)
|
|
|
|
@field_validator("title")
|
|
@classmethod
|
|
def strip_title(cls, value: str) -> str:
|
|
title = value.strip()
|
|
if not title:
|
|
raise ValueError("Title cannot be empty")
|
|
return title
|
|
|
|
@field_validator("description")
|
|
@classmethod
|
|
def normalize_description(cls, value: Optional[str]) -> Optional[str]:
|
|
if value is None:
|
|
return None
|
|
description = value.strip()
|
|
return description or None
|
|
|
|
@model_validator(mode="after")
|
|
def validate_reward(self) -> "PollCreateRequest":
|
|
if self.reward_enabled and self.reward_amount_kopeks <= 0:
|
|
raise ValueError("Reward amount must be positive when rewards are enabled")
|
|
if not self.reward_enabled:
|
|
self.reward_amount_kopeks = 0
|
|
return self
|
|
|
|
|
|
class PollQuestionOptionResponse(BaseModel):
|
|
id: int
|
|
text: str
|
|
order: int
|
|
|
|
|
|
class PollQuestionResponse(BaseModel):
|
|
id: int
|
|
text: str
|
|
order: int
|
|
options: list[PollQuestionOptionResponse]
|
|
|
|
|
|
class PollSummaryResponse(BaseModel):
|
|
id: int
|
|
title: str
|
|
description: Optional[str]
|
|
reward_enabled: bool
|
|
reward_amount_kopeks: int
|
|
reward_amount_rubles: float
|
|
questions_count: int
|
|
responses_count: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class PollDetailResponse(BaseModel):
|
|
id: int
|
|
title: str
|
|
description: Optional[str]
|
|
reward_enabled: bool
|
|
reward_amount_kopeks: int
|
|
reward_amount_rubles: float
|
|
questions: list[PollQuestionResponse]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class PollListResponse(BaseModel):
|
|
items: list[PollSummaryResponse]
|
|
total: int
|
|
limit: int
|
|
offset: int
|
|
|
|
|
|
class PollOptionStats(BaseModel):
|
|
id: int
|
|
text: str
|
|
count: int
|
|
|
|
|
|
class PollQuestionStats(BaseModel):
|
|
id: int
|
|
text: str
|
|
order: int
|
|
options: list[PollOptionStats]
|
|
|
|
|
|
class PollStatisticsResponse(BaseModel):
|
|
poll_id: int
|
|
poll_title: str
|
|
total_responses: int
|
|
completed_responses: int
|
|
reward_sum_kopeks: int
|
|
reward_sum_rubles: float
|
|
questions: list[PollQuestionStats]
|
|
|
|
|
|
class PollAnswerResponse(BaseModel):
|
|
question_id: Optional[int]
|
|
question_text: Optional[str]
|
|
option_id: Optional[int]
|
|
option_text: Optional[str]
|
|
created_at: datetime
|
|
|
|
|
|
class PollUserResponse(BaseModel):
|
|
id: int
|
|
user_id: Optional[int]
|
|
user_telegram_id: Optional[int]
|
|
user_username: Optional[str]
|
|
sent_at: datetime
|
|
started_at: Optional[datetime]
|
|
completed_at: Optional[datetime]
|
|
reward_given: bool
|
|
reward_amount_kopeks: int
|
|
reward_amount_rubles: float
|
|
answers: list[PollAnswerResponse]
|
|
|
|
|
|
class PollResponsesListResponse(BaseModel):
|
|
items: list[PollUserResponse]
|
|
total: int
|
|
limit: int
|
|
offset: int
|
|
|
|
|
|
class PollSendRequest(BaseModel):
|
|
target: str = Field(
|
|
...,
|
|
description=(
|
|
"Аудитория для отправки опроса (например: all, active, trial, "
|
|
"custom_today и т.д.)"
|
|
),
|
|
max_length=100,
|
|
)
|
|
|
|
|
|
class PollSendResponse(BaseModel):
|
|
poll_id: int
|
|
target: str
|
|
sent: int
|
|
failed: int
|
|
skipped: int
|
|
total: int
|