"""Обратная связь по порталу: отправить может любой сотрудник, читать и отвечать — только администраторы портала.""" from datetime import timedelta from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import func, select from sqlalchemy.orm import Session from .. import config, push, schemas as S from ..db import get_db from ..models import Feedback, User from ..security import current_user, is_portal_admin, require_portal_admin from .catalog import commit, get_or_404 from .inventory import user_names router = APIRouter(tags=["feedback"]) PER_HOUR = 20 # не больше сообщений в час от одного сотрудника KIND_RU = {"idea": "Идея", "bug": "Ошибка", "other": "Другое"} def fb_out(f: Feedback, names: dict) -> S.FeedbackOut: o = S.FeedbackOut.model_validate(f) o.user_name, o.answered_by_name = names.get(f.user_id), names.get(f.answered_by_id) return o @router.post("/feedback", status_code=201) def send(data: S.FeedbackIn, db: Session = Depends(get_db), u: User = Depends(current_user)): since = config.now() - timedelta(hours=1) if db.scalar(select(func.count()).select_from(Feedback).where(Feedback.user_id == u.id, Feedback.created_at >= since)) >= PER_HOUR: raise HTTPException(429, "Слишком много сообщений, попробуйте позже") f = Feedback(user_id=u.id, kind=data.kind, text=data.text.strip(), page=data.page.strip()[:200], client=data.client) db.add(f) commit(db) admins = [x.id for x in db.scalars(select(User).where(User.is_active.is_(True), User.is_admin.is_(True))) if is_portal_admin(x) and x.id != u.id] push.notify(admins, f"Обратная связь: {KIND_RU.get(f.kind, f.kind)}", f"{u.full_name or u.username}: {f.text[:100]}", "/#/feedback") return {"ok": True, "id": f.id} @router.get("/feedback", response_model=list[S.FeedbackOut]) def list_(status: Optional[str] = Query(None, pattern="^(new|taken|done|rejected|open)$"), limit: int = Query(300, ge=1, le=2000), db: Session = Depends(get_db), _: User = Depends(require_portal_admin)): stmt = select(Feedback).order_by(Feedback.created_at.desc(), Feedback.id.desc()).limit(limit) if status == "open": stmt = stmt.where(Feedback.status.in_(("new", "taken"))) elif status: stmt = stmt.where(Feedback.status == status) names = user_names(db) return [fb_out(f, names) for f in db.scalars(stmt)] @router.patch("/feedback/{id}", response_model=S.FeedbackOut) def update(id: int, data: S.FeedbackPatch, db: Session = Depends(get_db), u: User = Depends(require_portal_admin)): f = get_or_404(db, Feedback, id) if data.status is not None: f.status = data.status if data.answer is not None: f.answer = data.answer.strip() f.answered_by_id, f.answered_at = u.id, config.now() commit(db) return fb_out(f, user_names(db)) @router.delete("/feedback/{id}", status_code=204) def delete(id: int, db: Session = Depends(get_db), _: User = Depends(require_portal_admin)): db.delete(get_or_404(db, Feedback, id)) commit(db)