From 6c50e7e2fe669b1442a1591aa98e8690e20ed6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Fri, 24 Apr 2026 14:45:39 +0300 Subject: [PATCH] =?UTF-8?q?1.=20=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D1=82=D0=BD=D0=BE=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=83=D1=87=D0=B5=D1=82=D0=BA=D0=B8=20=D0=B8=D0=B7=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B7=D1=8B,=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=D0=BD=20=D0=BF=D0=B0=D1=80=D1=81=D0=B5=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D0=B0=D0=B7=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 9a82c3f..a0a2359 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ -from fastapi import FastAPI, Form, Request, Query, Body, HTTPException, WebSocket, WebSocketDisconnect -from datetime import datetime, timezone +from fastapi import FastAPI, Form, Request, Query, HTTPException, WebSocket, WebSocketDisconnect +from datetime import datetime from fastapi.responses import ( HTMLResponse, RedirectResponse, @@ -10,15 +10,21 @@ from fastapi.responses import ( from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field -from collections import defaultdict -from typing import List, Optional, Dict, Any -import json +from typing import List, Dict from pathlib import Path import io import asyncio import time from urllib.parse import quote +import traceback +import contextlib +from parsers.parser_game import run_parser_game +from parsers.parser_players import run_parser_players +from parsers.parser_schedule import run_parser_schedule +from parsers.parser_standings import run_parser_standings + +from db import get_connection from repositories.match_formation_repository import ( get_match_formations, replace_match_formations, @@ -85,6 +91,7 @@ from repositories.match_event_repository import ( from repositories.auth_repository import get_user_by_username from services.auth_service import ( create_auth_session, + hash_password, verify_password, get_current_user_from_request, build_not_authenticated_response, @@ -1660,4 +1667,134 @@ def info_page(request: Request): name="info.html", request=request, context={} - ) \ No newline at end of file + ) + +def render_admin_db_index(request: Request, parser_output: str | None = None, parser_name: str | None = None, parser_success: bool | None = None): + return templates.TemplateResponse( + name="admin_db_index.html", + request=request, + context={ + "current_user": getattr(request.state, "current_user", None), + "parser_output": parser_output, + "parser_name": parser_name, + "parser_success": parser_success, + }, + ) + + +@app.get("/admin/db", response_class=HTMLResponse) +def admin_db_index(request: Request): + return render_admin_db_index(request) + + +@app.post("/admin/db/run-parser", response_class=HTMLResponse) +def admin_db_run_parser(request: Request, parser_name: str = Form(...)): + current_user = getattr(request.state, "current_user", None) + if not current_user or current_user.get("role") != "admin": + return RedirectResponse(url="/login", status_code=303) + + parser_map = { + "players": ("Игроки", run_parser_players), + "schedule": ("Расписание", run_parser_schedule), + "standings": ("Турнирка", run_parser_standings), + } + + parser_meta = parser_map.get(parser_name) + if not parser_meta: + return render_admin_db_index( + request, + parser_output="Неизвестный парсер.", + parser_name=parser_name, + parser_success=False, + ) + + title, parser_func = parser_meta + buffer = io.StringIO() + success = True + + with contextlib.redirect_stdout(buffer), contextlib.redirect_stderr(buffer): + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Запуск парсера: {title}") + try: + parser_func() + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Парсер завершён успешно") + except Exception: + success = False + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Ошибка при выполнении парсера") + print(traceback.format_exc()) + + output = buffer.getvalue().strip() or "Парсер не вернул сообщений." + return render_admin_db_index( + request, + parser_output=output, + parser_name=title, + parser_success=success, + ) + + + + +@app.post("/admin/db/create-account", response_class=HTMLResponse) +def admin_db_create_account( + request: Request, + username: str = Form(...), + password: str = Form(...), + role: str = Form(default="operator"), +): + current_user = getattr(request.state, "current_user", None) + if not current_user or current_user.get("role") != "admin": + return RedirectResponse(url="/login", status_code=303) + + username = username.strip() + role = (role or "operator").strip().lower() + + if not username: + return render_admin_db_index(request, account_output="Логин обязателен.", account_success=False) + + if not password: + return render_admin_db_index(request, account_output="Пароль обязателен.", account_success=False) + + if len(password) < 6: + return render_admin_db_index(request, account_output="Пароль должен быть не короче 6 символов.", account_success=False) + + if role not in {"admin", "operator"}: + return render_admin_db_index(request, account_output="Роль должна быть admin или operator.", account_success=False) + + password_hash = hash_password(password) + conn = get_connection() + + try: + with conn.cursor() as cur: + cur.execute( + """ + INSERT INTO admin_users (username, password_hash, role, is_active) + VALUES (%s, %s, %s, TRUE) + ON CONFLICT (username) DO NOTHING + RETURNING id; + """, + (username, password_hash, role), + ) + row = cur.fetchone() + conn.commit() + + if row: + return render_admin_db_index( + request, + account_output=f"Пользователь создан: {username} (id={row[0]})\nРоль: {role}", + account_success=True, + ) + + return render_admin_db_index( + request, + account_output=f"Пользователь '{username}' уже существует.", + account_success=False, + ) + + except Exception: + conn.rollback() + return render_admin_db_index( + request, + account_output="Ошибка при создании аккаунта:\n" + traceback.format_exc(), + account_success=False, + ) + finally: + conn.close() \ No newline at end of file