Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Демонстрационный режим — персональный: в демо-БД работают только те, кто его включил (или кого включил администратор).
|
||||
Остальные в это время работают с боевыми данными. Подробности — app/demo.py, app/mode.py."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import demo, mode, schemas as S
|
||||
from ..db import get_db, session_for
|
||||
from ..models import User
|
||||
from ..security import current_user, require_admin
|
||||
from .catalog import issue_tokens, tokens_for_real
|
||||
|
||||
router = APIRouter(prefix="/demo", tags=["demo"])
|
||||
|
||||
|
||||
class ConfirmIn(BaseModel):
|
||||
confirm: bool = False
|
||||
|
||||
|
||||
class ImpersonateIn(BaseModel):
|
||||
user_id: int
|
||||
|
||||
|
||||
class UserDemoIn(BaseModel):
|
||||
on: bool
|
||||
|
||||
|
||||
def real_admin(request: Request, u: User = Depends(require_admin)) -> User:
|
||||
"""Администратор в боевой БД (запрос может прийти и из демо-сессии — тогда берём его настоящую учётную запись)."""
|
||||
rid = getattr(request.state, "real_user_id", None)
|
||||
if rid is None:
|
||||
raise HTTPException(403, "Демонстрационная учётная запись не может управлять демо-режимом")
|
||||
with session_for(False) as s:
|
||||
ru = s.get(User, rid)
|
||||
if not ru or not ru.is_admin or not ru.is_active:
|
||||
raise HTTPException(403, "Только для администратора")
|
||||
s.expunge(ru)
|
||||
return ru
|
||||
|
||||
|
||||
def _real_tokens(real_id: int, device: str) -> S.TokenOut:
|
||||
with session_for(False) as s:
|
||||
u = s.get(User, real_id)
|
||||
if not u or not u.is_active:
|
||||
raise HTTPException(404, "Пользователь не найден")
|
||||
return tokens_for_real(s, u, device)
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def status():
|
||||
"""Публичный: экрану входа — есть ли сейчас демо-учётные записи. Сами записи здесь не раскрываются."""
|
||||
return {"active": mode.State.active}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
def my_demo(request: Request, u: User = Depends(current_user)):
|
||||
"""Для баннера: в демо ли текущая сессия и можно ли из него выйти самому."""
|
||||
return {"in_demo": bool(getattr(request.state, "demo", False)),
|
||||
"can_leave": getattr(request.state, "real_user_id", None) is not None}
|
||||
|
||||
|
||||
@router.get("")
|
||||
def info(request: Request, db: Session = Depends(get_db), admin: User = Depends(real_admin)):
|
||||
"""Панель администратора: состояние демо-БД, кто сейчас в демо, демо-учётные записи."""
|
||||
with session_for(False) as s:
|
||||
people = [{"id": x.id, "username": x.username, "full_name": x.full_name, "is_admin": x.is_admin,
|
||||
"in_demo": mode.user_in_demo(x.id)} for x in s.scalars(select(User).where(User.is_active.is_(True)).order_by(User.full_name, User.username))]
|
||||
out = {"active": mode.State.active, "since": mode.State.since, "in_demo": bool(getattr(request.state, "demo", False)),
|
||||
"users": people, "password": demo.DEMO_PASSWORD}
|
||||
if mode.State.active:
|
||||
with session_for(True) as ds:
|
||||
out["accounts"] = demo.demo_accounts(ds)
|
||||
return out
|
||||
|
||||
|
||||
@router.post("/enable", response_model=S.TokenOut)
|
||||
def enable(data: ConfirmIn, admin: User = Depends(real_admin)):
|
||||
"""Войти в демо самому. Боевые данные не меняются, остальные пользователи продолжают работать как обычно."""
|
||||
if not data.confirm:
|
||||
raise HTTPException(422, "Требуется подтверждение")
|
||||
try:
|
||||
demo.enable_for(admin, owner=admin)
|
||||
except demo.DemoError as e:
|
||||
raise HTTPException(409, str(e))
|
||||
return _real_tokens(admin.id, "demo")
|
||||
|
||||
|
||||
@router.post("/leave", response_model=S.TokenOut)
|
||||
def leave(request: Request, u: User = Depends(current_user)):
|
||||
"""Выйти из демо (любой пользователь, которого перевели в демо) и вернуться к боевым данным."""
|
||||
rid = getattr(request.state, "real_user_id", None)
|
||||
if rid is None:
|
||||
raise HTTPException(409, "Это демонстрационная учётная запись — просто выйдите из неё")
|
||||
demo.disable_for(rid)
|
||||
return _real_tokens(rid, "web")
|
||||
|
||||
|
||||
@router.post("/users/{rid}")
|
||||
def set_user(rid: int, data: UserDemoIn, admin: User = Depends(real_admin)):
|
||||
"""Включить или выключить демо другому пользователю (например, если кто-то случайно включил и не может выйти)."""
|
||||
with session_for(False) as s:
|
||||
target = s.get(User, rid)
|
||||
if not target or not target.is_active:
|
||||
raise HTTPException(404, "Пользователь не найден")
|
||||
s.expunge(target)
|
||||
if data.on:
|
||||
demo.enable_for(target, owner=admin)
|
||||
else:
|
||||
demo.disable_for(rid)
|
||||
return {"id": rid, "in_demo": mode.user_in_demo(rid)}
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
def reset(db: Session = Depends(get_db), admin: User = Depends(real_admin)):
|
||||
"""Пересоздать тестовые данные (кто в демо — остаётся в демо)."""
|
||||
try:
|
||||
db.close() # освободить демо-БД до удаления файла (важно на Windows)
|
||||
demo.reset(admin)
|
||||
except demo.DemoError as e:
|
||||
raise HTTPException(409, str(e))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/destroy")
|
||||
def destroy(db: Session = Depends(get_db), admin: User = Depends(real_admin)):
|
||||
"""Удалить демо-данные целиком: все возвращаются к боевым данным."""
|
||||
try:
|
||||
db.close() # освободить демо-БД до удаления файла (важно на Windows)
|
||||
demo.destroy()
|
||||
except demo.DemoError as e:
|
||||
raise HTTPException(409, str(e))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/impersonate", response_model=S.TokenOut)
|
||||
def impersonate(data: ImpersonateIn, request: Request, admin: User = Depends(real_admin)):
|
||||
"""Войти под демо-пользователем без пароля (показать роли). Только в демо-сессии; администраторов выбрать нельзя."""
|
||||
if not getattr(request.state, "demo", False):
|
||||
raise HTTPException(409, "Сначала войдите в демо-режим")
|
||||
with session_for(True) as ds:
|
||||
target = ds.get(User, data.user_id)
|
||||
if not target or target.is_admin or not target.is_active:
|
||||
raise HTTPException(404, "Пользователь не найден")
|
||||
return issue_tokens(ds, target, "demo-impersonate")
|
||||
Reference in New Issue
Block a user