тест 8

This commit is contained in:
2026-05-15 14:28:14 +03:00
parent a8947556a5
commit 4e6806c553

151
app.py
View File

@@ -1,4 +1,12 @@
from fastapi import FastAPI, Form, Request, Query, HTTPException, WebSocket, WebSocketDisconnect
from fastapi import (
FastAPI,
Form,
Request,
Query,
HTTPException,
WebSocket,
WebSocketDisconnect,
)
from datetime import datetime
from fastapi.responses import (
HTMLResponse,
@@ -125,20 +133,18 @@ app = FastAPI(
docs_url=None, # ❌ отключает /docs
redoc_url=None, # ❌ отключает /redoc
openapi_url=None, # ❌ отключает /openapi.json
)
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
@app.on_event("startup")
def start_scheduler():
import threading
thread = threading.Thread(
target=run_scheduler,
kwargs={"with_signal_handlers": False},
daemon=True
target=run_scheduler, kwargs={"with_signal_handlers": False}, daemon=True
)
thread.start()
@@ -288,6 +294,7 @@ class VmixConnectionManager:
vmix_ws_manager = VmixConnectionManager()
def get_current_user(request: Request):
return getattr(request.state, "current_user", None)
@@ -304,6 +311,7 @@ def require_role(request: Request, allowed_roles: set[str]):
return RedirectResponse("/admin/matches", status_code=303)
return None
@app.get("/")
def root(request: Request):
user = get_current_user_from_request(request)
@@ -549,14 +557,18 @@ def select_match(
},
)
return JSONResponse({
"success": True,
"session_token": session_token,
"session_url": f"/admin/session/{session_token}",
})
return JSONResponse(
{
"success": True,
"session_token": session_token,
"session_url": f"/admin/session/{session_token}",
}
)
@app.get("/admin/session/{session_token}/download-vmix-page", response_class=HTMLResponse)
@app.get(
"/admin/session/{session_token}/download-vmix-page", response_class=HTMLResponse
)
def download_vmix_project_page(request: Request, session_token: str):
session_row = get_match_session_by_token(session_token)
if not session_row:
@@ -635,7 +647,6 @@ def session_workspace(
home_formations = get_match_formations(match_id, home_team_id)
away_formations = get_match_formations(match_id, away_team_id)
return templates.TemplateResponse(
name="match_workspace.html",
request=request,
@@ -1404,39 +1415,11 @@ def vmix_standings(session_token: str):
for row in rows
]
class MatchChannelPayload(BaseModel):
match_external_id: str
channel: str = ""
@app.post("/admin/session/{session_token}/schedule/channel")
def update_schedule_channel(session_token: str, payload: MatchChannelPayload):
session_row = get_match_session_by_token(session_token)
if not session_row:
return JSONResponse({"error": "session_not_found"}, status_code=404)
allowed = {"", "match", "match_premier", "match_and_premier"}
if payload.channel not in allowed:
return JSONResponse({"error": "invalid_channel"}, status_code=400)
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
UPDATE matches
SET channel = %s
WHERE external_id = %s
""",
(payload.channel, payload.match_external_id),
)
conn.commit()
return {"success": True}
except Exception as e:
conn.rollback()
return JSONResponse({"error": str(e)}, status_code=500)
finally:
conn.close()
@app.get("/vmix/session/{session_token}/schedule")
def vmix_schedule(session_token: str):
@@ -1462,9 +1445,17 @@ def vmix_schedule(session_token: str):
"color": (
"#37F193" if row[5] not in ["scheduled", "finished"] else "#FFFFFF00"
),
"mask_time": "#FFFFFF00" if row[2] is not None and row[3] is not None else "#FFFFFF",
"mask_score": "#FFFFFF00" if row[2] is None and row[3] is None else "#FFFFFF",
"channel": row[7] or "",
"mask_time": (
"#FFFFFF00" if row[2] is not None and row[3] is not None else "#FFFFFF"
),
"mask_score": (
"#FFFFFF00" if row[2] is None and row[3] is None else "#FFFFFF"
),
"channel": (
rf"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Лого каналов\{row[7]}.png"
if row[7]
else r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Лого каналов\EMPTY.png"
),
}
for row in rows
]
@@ -1590,6 +1581,7 @@ def vmix_match_events(session_token: str):
return new_events
@app.websocket("/ws/vmix-client")
async def ws_vmix_client(
websocket: WebSocket,
@@ -1609,13 +1601,15 @@ async def ws_vmix_client(
)
try:
await websocket.send_json({
"type": "connected",
"client_id": client_id,
"match_id": match_id,
"session_token": session_token,
"operator_name": operator_name,
})
await websocket.send_json(
{
"type": "connected",
"client_id": client_id,
"match_id": match_id,
"session_token": session_token,
"operator_name": operator_name,
}
)
while True:
msg = await websocket.receive_json()
@@ -1635,7 +1629,9 @@ async def ws_vmix_client(
operator_name=msg.get("operator_name"),
group_name=msg.get("group_name"),
)
await websocket.send_json({"type": "registered", "client_id": client_id})
await websocket.send_json(
{"type": "registered", "client_id": client_id}
)
continue
if msg_type == "vmix_result":
@@ -1652,6 +1648,7 @@ async def ws_vmix_client(
except Exception:
pass
@app.post("/api/vmix/publish-command")
async def publish_vmix_command(payload: PublishVmixCommandPayload, request: Request):
commands = [str(x).strip() for x in (payload.commands or []) if str(x).strip()]
@@ -1700,20 +1697,24 @@ async def publish_vmix_command(payload: PublishVmixCommandPayload, request: Requ
"commands_count": len(commands),
}
@app.get("/api/vmix/clients")
async def list_vmix_clients():
clients = await vmix_ws_manager.list_clients()
return {"items": clients}
@app.get("/info", response_class=HTMLResponse)
def info_page(request: Request):
return templates.TemplateResponse(
name="info.html",
request=request,
context={}
)
return templates.TemplateResponse(name="info.html", request=request, context={})
def render_admin_db_index(request: Request, parser_output: str | None = None, parser_name: str | None = None, parser_success: bool | None = None):
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,
@@ -1757,13 +1758,19 @@ def admin_db_run_parser(request: Request, parser_name: str = Form(...)):
success = True
with contextlib.redirect_stdout(buffer), contextlib.redirect_stderr(buffer):
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Запуск парсера: {title}")
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')}] Парсер завершён успешно")
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(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Ошибка при выполнении парсера"
)
print(traceback.format_exc())
output = buffer.getvalue().strip() or "Парсер не вернул сообщений."
@@ -1775,8 +1782,6 @@ def admin_db_run_parser(request: Request, parser_name: str = Form(...)):
)
@app.post("/admin/db/create-account", response_class=HTMLResponse)
def admin_db_create_account(
request: Request,
@@ -1792,16 +1797,28 @@ def admin_db_create_account(
role = (role or "operator").strip().lower()
if not username:
return render_admin_db_index(request, parser_output="Логин обязателен.", parser_success=False)
return render_admin_db_index(
request, parser_output="Логин обязателен.", parser_success=False
)
if not password:
return render_admin_db_index(request, parser_output="Пароль обязателен.", parser_success=False)
return render_admin_db_index(
request, parser_output="Пароль обязателен.", parser_success=False
)
if len(password) < 6:
return render_admin_db_index(request, parser_output="Пароль должен быть не короче 6 символов.", parser_success=False)
return render_admin_db_index(
request,
parser_output="Пароль должен быть не короче 6 символов.",
parser_success=False,
)
if role not in {"admin", "operator"}:
return render_admin_db_index(request, parser_output="Роль должна быть admin или operator.", parser_success=False)
return render_admin_db_index(
request,
parser_output="Роль должна быть admin или operator.",
parser_success=False,
)
password_hash = hash_password(password)
conn = get_connection()