Add OBS widgets for streamers

- Add widget token authentication system
- Create leaderboard, current assignment, and progress widgets
- Support dark, light, and neon themes
- Add widget settings modal for URL generation
- Fix avatar loading through backend API proxy

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-09 19:16:50 +03:00
parent cd78a99ce7
commit 146ed5e489
18 changed files with 2286 additions and 2 deletions

View File

@@ -134,6 +134,15 @@ from app.schemas.promo_code import (
PromoCodeRedemptionUser,
)
from app.schemas.user import ShopItemPublic
from app.schemas.widget import (
WidgetTokenCreate,
WidgetTokenResponse,
WidgetTokenListItem,
WidgetLeaderboardEntry,
WidgetLeaderboardResponse,
WidgetCurrentResponse,
WidgetProgressResponse,
)
__all__ = [
# User
@@ -260,4 +269,12 @@ __all__ = [
"PromoCodeRedeemResponse",
"PromoCodeRedemptionResponse",
"PromoCodeRedemptionUser",
# Widget
"WidgetTokenCreate",
"WidgetTokenResponse",
"WidgetTokenListItem",
"WidgetLeaderboardEntry",
"WidgetLeaderboardResponse",
"WidgetCurrentResponse",
"WidgetProgressResponse",
]

View File

@@ -0,0 +1,79 @@
from pydantic import BaseModel
from datetime import datetime
# === Token schemas ===
class WidgetTokenCreate(BaseModel):
"""Создание токена виджета"""
pass # Не требует параметров
class WidgetTokenResponse(BaseModel):
"""Ответ с токеном виджета"""
id: int
token: str
created_at: datetime
expires_at: datetime | None
is_active: bool
urls: dict[str, str] # Готовые URL для виджетов
class Config:
from_attributes = True
class WidgetTokenListItem(BaseModel):
"""Элемент списка токенов"""
id: int
token: str
created_at: datetime
is_active: bool
class Config:
from_attributes = True
# === Widget data schemas ===
class WidgetLeaderboardEntry(BaseModel):
"""Запись в лидерборде виджета"""
rank: int
nickname: str
avatar_url: str | None
total_points: int
current_streak: int
is_current_user: bool # Для подсветки
class WidgetLeaderboardResponse(BaseModel):
"""Ответ лидерборда для виджета"""
entries: list[WidgetLeaderboardEntry]
current_user_rank: int | None
total_participants: int
marathon_title: str
class WidgetCurrentResponse(BaseModel):
"""Текущее задание для виджета"""
has_assignment: bool
game_title: str | None = None
game_cover_url: str | None = None
assignment_type: str | None = None # "challenge" | "playthrough"
challenge_title: str | None = None
challenge_description: str | None = None
points: int | None = None
difficulty: str | None = None # easy, medium, hard
bonus_completed: int | None = None # Для прохождений
bonus_total: int | None = None
class WidgetProgressResponse(BaseModel):
"""Прогресс участника для виджета"""
nickname: str
avatar_url: str | None
rank: int
total_points: int
current_streak: int
completed_count: int
dropped_count: int
marathon_title: str