Files
site_aegisone/py_service/app/documents_router.py
T

292 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import re
import markdown
from pathlib import Path
from fastapi import APIRouter, Request, Depends, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from datetime import datetime
from app.config import get_settings
from app.services.docs_permissions import load_permissions, save_permissions, discover_docs, can_access_doc, get_allowed_docs
from app.dependencies import get_current_user, require_role
settings = get_settings()
router = APIRouter(prefix="/service/documents", tags=["documents"])
from app.templating import templates
def now_str():
return datetime.now().strftime("%d.%m.%Y %H:%M")
def _doc_ctx(request, user, **kw):
original_user = request.session.get("original_user")
available_roles = {"owner": ["owner", "engineer", "technician"], "engineer": ["engineer", "technician"]}
c = {"request": request, "user": user, "now": now_str(),
"role_permissions": getattr(request.state, 'role_permissions', {}),
"role_override_active": original_user is not None,
"original_role": original_user["role"] if original_user else None,
"available_roles": available_roles.get(user["role"], [user["role"]]) if user else []}
c.update(kw)
return c
def transliterate_slug(text: str) -> str:
translit_map = {
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 'ж': 'zh',
'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o',
'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts',
'ч': 'ch', 'ш': 'sh', 'щ': 'shch', 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu',
'я': 'ya', ' ': '_',
}
result = ''.join(translit_map.get(c.lower(), c) for c in text)
result = re.sub(r'[^a-zA-Z0-9_\-]', '', result)
result = re.sub(r'_+', '_', result)
return result.strip('_').lower()
def render_markdown(content: str) -> str:
md = markdown.Markdown(extensions=['tables', 'fenced_code', 'toc', 'attr_list'])
return md.convert(content)
@router.get("/")
async def documents_list(
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
docs = get_allowed_docs(user["role"])
return templates.TemplateResponse("documents/list.html", _doc_ctx(request, user, docs=docs))
@router.get("/admin/permissions")
async def permissions_page(
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] != "owner":
return HTMLResponse(content="Доступ запрещён", status_code=403)
perms = discover_docs()
return templates.TemplateResponse("documents/permissions.html", _doc_ctx(request, user, perms=perms))
@router.post("/admin/permissions")
async def permissions_update(
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] != "owner":
return HTMLResponse(content="Доступ запрещён", status_code=403)
form = await request.form()
perms = discover_docs()
for slug in perms.keys():
engineer_view = form.get(f"{slug}_engineer_view") == "on"
engineer_edit = form.get(f"{slug}_engineer_edit") == "on"
technician_view = form.get(f"{slug}_technician_view") == "on"
if engineer_edit and not engineer_view:
engineer_view = True
perms[slug]["permissions"]["engineer"] = {
"view": engineer_view,
"edit": engineer_edit,
"cancel": engineer_edit,
}
perms[slug]["permissions"]["technician"] = {
"view": technician_view,
}
save_permissions(perms)
return RedirectResponse(url="/service/documents/admin/permissions", status_code=302)
@router.post("/admin/create")
async def document_create(
request: Request,
title: str = Form(...),
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] != "owner":
return HTMLResponse(content="Доступ запрещён", status_code=403)
slug = transliterate_slug(title)
if not slug:
return HTMLResponse(content="Некорректное название", status_code=400)
docs_dir = settings.DOCS_DIR
os.makedirs(docs_dir, exist_ok=True)
filename = f"{slug}.md"
filepath = os.path.join(docs_dir, filename)
if os.path.exists(filepath):
return HTMLResponse(content="Документ уже существует", status_code=400)
with open(filepath, "w", encoding="utf-8") as f:
f.write(f"# {title}\n\n")
perms = load_permissions()
perms[slug] = {
"title": title,
"filename": filename,
"sort_order": len(perms),
"permissions": {
"engineer": {"view": False, "edit": False, "cancel": False},
"technician": {"view": False},
},
}
save_permissions(perms)
return RedirectResponse(url=f"/service/documents/{slug}/edit", status_code=302)
@router.get("/tech-access")
async def tech_access_page(
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] not in ("engineer", "owner"):
return HTMLResponse(content="Доступ только для инженеров и руководителей", status_code=403)
perms = discover_docs()
editable_docs = [
doc for slug, doc in perms.items()
if doc.get("permissions", {}).get("engineer", {}).get("edit", False)
]
return templates.TemplateResponse("documents/tech_access.html", _doc_ctx(request, user, docs=editable_docs, perms=perms))
@router.post("/tech-access")
async def tech_access_update(
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] not in ("engineer", "owner"):
return HTMLResponse(content="Доступ только для инженеров и руководителей", status_code=403)
form = await request.form()
perms = discover_docs()
for slug in perms.keys():
if not perms[slug].get("permissions", {}).get("engineer", {}).get("edit", False):
continue
technician_view = form.get(f"{slug}_technician_view") == "on"
perms[slug]["permissions"]["technician"] = {"view": technician_view}
save_permissions(perms)
return RedirectResponse(url="/service/documents/tech-access", status_code=302)
@router.get("/{slug}")
async def document_view(
slug: str,
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if not can_access_doc(user["role"], slug, "view"):
return HTMLResponse(content="Доступ запрещён", status_code=403)
docs_dir = settings.DOCS_DIR
perms = discover_docs()
doc = perms.get(slug)
if not doc:
return HTMLResponse(content="Документ не найден", status_code=404)
filepath = os.path.join(docs_dir, doc["filename"])
if not os.path.exists(filepath):
return HTMLResponse(content="Файл не найден", status_code=404)
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
html_content = render_markdown(content)
return templates.TemplateResponse("documents/view.html", _doc_ctx(request, user,
doc=doc, content=html_content,
can_edit=can_access_doc(user["role"], slug, "edit"),
can_download=user["role"] == "owner",
))
@router.get("/{slug}/edit")
async def document_edit(
slug: str,
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if not can_access_doc(user["role"], slug, "edit"):
return HTMLResponse(content="Доступ запрещён", status_code=403)
docs_dir = settings.DOCS_DIR
perms = discover_docs()
doc = perms.get(slug)
if not doc:
return HTMLResponse(content="Документ не найден", status_code=404)
filepath = os.path.join(docs_dir, doc["filename"])
if not os.path.exists(filepath):
return HTMLResponse(content="Файл не найден", status_code=404)
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
return templates.TemplateResponse("documents/edit.html", _doc_ctx(request, user, doc=doc, content=content))
@router.post("/{slug}/edit")
async def document_update(
slug: str,
request: Request,
content: str = Form(...),
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if not can_access_doc(user["role"], slug, "edit"):
return HTMLResponse(content="Доступ запрещён", status_code=403)
docs_dir = settings.DOCS_DIR
perms = discover_docs()
doc = perms.get(slug)
if not doc:
return HTMLResponse(content="Документ не найден", status_code=404)
filepath = os.path.join(docs_dir, doc["filename"])
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
return RedirectResponse(url=f"/service/documents/{slug}", status_code=302)
@router.get("/{slug}/download")
async def document_download(
slug: str,
request: Request,
db: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user),
):
if user["role"] != "owner":
return HTMLResponse(content="Скачивание доступно только владельцу", status_code=403)
from fastapi.responses import FileResponse
docs_dir = settings.DOCS_DIR
perms = discover_docs()
doc = perms.get(slug)
if not doc:
return HTMLResponse(content="Документ не найден", status_code=404)
filepath = os.path.join(docs_dir, doc["filename"])
if not os.path.exists(filepath):
return HTMLResponse(content="Файл не найден", status_code=404)
return FileResponse(filepath, filename=doc["filename"], media_type="text/markdown")