From 9014d5d79d01ab3a0d904ed45d3fd61287d3a2fa Mon Sep 17 00:00:00 2001 From: Oronemu Date: Sun, 4 Jan 2026 03:42:11 +0700 Subject: [PATCH] Add notification status to users table in AP --- backend/app/api/v1/admin.py | 118 +++++--------------- backend/app/schemas/admin.py | 4 + frontend/src/pages/admin/AdminUsersPage.tsx | 31 ++++- frontend/src/types/index.ts | 4 + 4 files changed, 64 insertions(+), 93 deletions(-) diff --git a/backend/app/api/v1/admin.py b/backend/app/api/v1/admin.py index b8671b6..ce8591f 100644 --- a/backend/app/api/v1/admin.py +++ b/backend/app/api/v1/admin.py @@ -64,6 +64,28 @@ async def log_admin_action( await db.commit() +def build_admin_user_response(user: User, marathons_count: int) -> AdminUserResponse: + """Build AdminUserResponse from User model.""" + return AdminUserResponse( + id=user.id, + login=user.login, + nickname=user.nickname, + role=user.role, + avatar_url=user.avatar_url, + telegram_id=user.telegram_id, + telegram_username=user.telegram_username, + marathons_count=marathons_count, + created_at=user.created_at.isoformat(), + is_banned=user.is_banned, + banned_at=user.banned_at.isoformat() if user.banned_at else None, + banned_until=user.banned_until.isoformat() if user.banned_until else None, + ban_reason=user.ban_reason, + notify_events=user.notify_events, + notify_disputes=user.notify_disputes, + notify_moderation=user.notify_moderation, + ) + + @router.get("/users", response_model=list[AdminUserResponse]) async def list_users( current_user: CurrentUser, @@ -97,21 +119,7 @@ async def list_users( marathons_count = await db.scalar( select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - response.append(AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=user.banned_at.isoformat() if user.banned_at else None, - banned_until=user.banned_until.isoformat() if user.banned_until else None, - ban_reason=user.ban_reason, - )) + response.append(build_admin_user_response(user, marathons_count)) return response @@ -130,21 +138,7 @@ async def get_user(user_id: int, current_user: CurrentUser, db: DbSession): select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - return AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=user.banned_at.isoformat() if user.banned_at else None, - banned_until=user.banned_until.isoformat() if user.banned_until else None, - ban_reason=user.ban_reason, - ) + return build_admin_user_response(user, marathons_count) @router.patch("/users/{user_id}/role", response_model=AdminUserResponse) @@ -184,21 +178,7 @@ async def set_user_role( select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - return AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=user.banned_at.isoformat() if user.banned_at else None, - banned_until=user.banned_until.isoformat() if user.banned_until else None, - ban_reason=user.ban_reason, - ) + return build_admin_user_response(user, marathons_count) @router.delete("/users/{user_id}", response_model=MessageResponse) @@ -363,21 +343,7 @@ async def ban_user( select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - return AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=user.banned_at.isoformat() if user.banned_at else None, - banned_until=user.banned_until.isoformat() if user.banned_until else None, - ban_reason=user.ban_reason, - ) + return build_admin_user_response(user, marathons_count) @router.post("/users/{user_id}/unban", response_model=AdminUserResponse) @@ -418,21 +384,7 @@ async def unban_user( select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - return AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=None, - banned_until=None, - ban_reason=None, - ) + return build_admin_user_response(user, marathons_count) # ============ Reset Password ============ @@ -478,21 +430,7 @@ async def reset_user_password( select(func.count()).select_from(Participant).where(Participant.user_id == user.id) ) - return AdminUserResponse( - id=user.id, - login=user.login, - nickname=user.nickname, - role=user.role, - avatar_url=user.avatar_url, - telegram_id=user.telegram_id, - telegram_username=user.telegram_username, - marathons_count=marathons_count, - created_at=user.created_at.isoformat(), - is_banned=user.is_banned, - banned_at=user.banned_at.isoformat() if user.banned_at else None, - banned_until=user.banned_until.isoformat() if user.banned_until else None, - ban_reason=user.ban_reason, - ) + return build_admin_user_response(user, marathons_count) # ============ Force Finish Marathon ============ diff --git a/backend/app/schemas/admin.py b/backend/app/schemas/admin.py index cadf302..6cefd7b 100644 --- a/backend/app/schemas/admin.py +++ b/backend/app/schemas/admin.py @@ -27,6 +27,10 @@ class AdminUserResponse(BaseModel): banned_at: str | None = None banned_until: str | None = None # None = permanent ban_reason: str | None = None + # Notification settings + notify_events: bool = True + notify_disputes: bool = True + notify_moderation: bool = True class Config: from_attributes = True diff --git a/frontend/src/pages/admin/AdminUsersPage.tsx b/frontend/src/pages/admin/AdminUsersPage.tsx index 5cda106..56c3a04 100644 --- a/frontend/src/pages/admin/AdminUsersPage.tsx +++ b/frontend/src/pages/admin/AdminUsersPage.tsx @@ -4,7 +4,7 @@ import type { AdminUser, UserRole } from '@/types' import { useToast } from '@/store/toast' import { useConfirm } from '@/store/confirm' import { NeonButton } from '@/components/ui' -import { Search, Ban, UserCheck, Shield, ShieldOff, ChevronLeft, ChevronRight, Users, X, KeyRound } from 'lucide-react' +import { Search, Ban, UserCheck, Shield, ShieldOff, ChevronLeft, ChevronRight, Users, X, KeyRound, Bell, BellOff } from 'lucide-react' export function AdminUsersPage() { const [users, setUsers] = useState([]) @@ -195,6 +195,7 @@ export function AdminUsersPage() { Роль Telegram Марафоны + Уведомления Статус Действия @@ -202,13 +203,13 @@ export function AdminUsersPage() { {loading ? ( - +
) : users.length === 0 ? ( - + Пользователи не найдены @@ -236,6 +237,30 @@ export function AdminUsersPage() { )} {user.marathons_count} + + {user.telegram_id ? ( +
+ {user.notify_events && user.notify_disputes && user.notify_moderation ? ( + + + Все + + ) : !user.notify_events && !user.notify_disputes && !user.notify_moderation ? ( + + + Откл + + ) : ( + + + Частично + + )} +
+ ) : ( + + )} + {user.is_banned ? ( diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 63d1e19..396df72 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -489,6 +489,10 @@ export interface AdminUser { banned_at: string | null banned_until: string | null // null = permanent ban ban_reason: string | null + // Notification settings + notify_events: boolean + notify_disputes: boolean + notify_moderation: boolean } export interface AdminMarathon {