klg-asutk-app/backend/app/api/routes/checklists.py
Yuriy aa052763f6 Безопасность и качество: 8 исправлений + обновления
- .env.example: полный шаблон, защита секретов
- .gitignore: явное исключение .env.* и секретов
- layout.tsx: XSS — заменён dangerouslySetInnerHTML на next/script для SW
- ESLint: no-console error (allow warn/error), ignore scripts/
- scripts/remove-console-logs.js: очистка console.log без glob
- backend/routes/modules: README с планом рефакторинга крупных файлов
- SECURITY.md: гид по секретам, XSS, CORS, auth, линту
- .husky/pre-commit: запуск npm run lint

+ прочие правки приложения и бэкенда

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 21:29:16 +03:00

106 lines
6.0 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.

"""Checklists API — refactored: pagination, audit, DRY."""
from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Query
from sqlalchemy.orm import Session
import csv, io
from app.api.deps import get_current_user, require_roles
from app.api.helpers import audit, paginate_query
from app.db.session import get_db
from app.models import ChecklistTemplate, ChecklistItem
from app.schemas.audit import ChecklistTemplateCreate, ChecklistTemplateOut, ChecklistItemCreate, ChecklistItemOut
router = APIRouter(tags=["checklists"])
def _template_with_items(template, db) -> ChecklistTemplateOut:
items = db.query(ChecklistItem).filter(ChecklistItem.template_id == template.id).order_by(ChecklistItem.sort_order).all()
out = ChecklistTemplateOut.model_validate(template)
out.items = [ChecklistItemOut.model_validate(i) for i in items]
return out
@router.get("/checklists/templates")
def list_templates(
domain: str | None = None, page: int = Query(1, ge=1), per_page: int = Query(25, ge=1, le=100),
db: Session = Depends(get_db), user=Depends(get_current_user),
):
q = db.query(ChecklistTemplate).filter(ChecklistTemplate.is_active == True)
if domain: q = q.filter(ChecklistTemplate.domain == domain)
q = q.order_by(ChecklistTemplate.name, ChecklistTemplate.version.desc())
result = paginate_query(q, page, per_page)
result["items"] = [_template_with_items(t, db) for t in result["items"]]
return result
@router.post("/checklists/templates", response_model=ChecklistTemplateOut, status_code=201,
dependencies=[Depends(require_roles("admin", "authority_inspector"))])
def create_template(payload: ChecklistTemplateCreate, db: Session = Depends(get_db), user=Depends(get_current_user)):
template = ChecklistTemplate(name=payload.name, version=payload.version, description=payload.description, domain=payload.domain)
db.add(template); db.flush()
if payload.items:
for d in payload.items:
db.add(ChecklistItem(template_id=template.id, code=d.code, text=d.text, domain=d.domain, sort_order=d.sort_order))
audit(db, user, "create", "checklist_template", description=f"Template: {payload.name}")
db.commit(); db.refresh(template)
return _template_with_items(template, db)
@router.post("/checklists/generate", response_model=ChecklistTemplateOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))])
def generate_checklist(source: str, name: str, items: list[ChecklistItemCreate] | None = None,
db: Session = Depends(get_db), user=Depends(get_current_user)):
template = ChecklistTemplate(name=name, version=1, domain=source)
db.add(template); db.flush()
if source == "fap_m_inspection":
items = [
ChecklistItemCreate(code="M.A.301", text="ВС имеет действующий сертификат лётной годности", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.302", text="ВС эксплуатируется в соответствии с CAME", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.303", text="Выполнены все требования по ТО", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.304", text="Все дефекты устранены или имеют разрешения", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.305", text="Документация по ВС актуальна", domain="ФАП-М"),
]
elif source != "custom" or not items:
raise HTTPException(400, "Invalid source or missing items")
for d in items:
db.add(ChecklistItem(template_id=template.id, code=d.code, text=d.text, domain=d.domain, sort_order=d.sort_order))
audit(db, user, "create", "checklist_template", description=f"Generated: {name}")
db.commit(); db.refresh(template)
return _template_with_items(template, db)
@router.get("/checklists/templates/{template_id}", response_model=ChecklistTemplateOut)
def get_template(template_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)):
t = db.query(ChecklistTemplate).filter(ChecklistTemplate.id == template_id).first()
if not t: raise HTTPException(404, "Not found")
return _template_with_items(t, db)
@router.post("/checklists/generate-from-csv", response_model=ChecklistTemplateOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))])
async def generate_from_csv(file: UploadFile = File(...), name: str | None = None, domain: str | None = None,
db: Session = Depends(get_db), user=Depends(get_current_user)):
content = await file.read()
try: text = content.decode('utf-8-sig')
except UnicodeDecodeError: text = content.decode('cp1251')
reader = csv.DictReader(io.StringIO(text))
fields = reader.fieldnames or []
rows = list(reader)
if not rows: raise HTTPException(400, "CSV empty")
code_col = next((c for c in fields if 'issue' in c.lower() and 'id' in c.lower()), None)
text_col = next((c for c in fields if 'summary' in c.lower() or 'description' in c.lower()), None)
if not code_col or not text_col: raise HTTPException(400, "Missing Issue Id / Summary columns")
domain_col = next((c for c in fields if 'domain' in c.lower()), None)
template = ChecklistTemplate(name=name or file.filename.replace('.csv', ''), version=1, domain=domain or "CSV")
db.add(template); db.flush()
count = 0
for idx, row in enumerate(rows):
code, txt = str(row.get(code_col, "")).strip(), str(row.get(text_col, "")).strip()
if not code or not txt: continue
db.add(ChecklistItem(template_id=template.id, code=code, text=txt,
domain=domain or (row.get(domain_col, "") if domain_col else None), sort_order=idx+1))
count += 1
if not count: db.rollback(); raise HTTPException(400, "No items created")
audit(db, user, "create", "checklist_template", description=f"CSV import: {count} items")
db.commit(); db.refresh(template)
return _template_with_items(template, db)