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"] != "engineer": 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"] != "engineer": 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")