"""admin 反馈页二维码卡配置:读 / 改文案开关 / 上传二维码 / 删二维码(带审计)。 整张卡存通用 app_config 表(见 app/repositories/feedback_qr.py),App 反馈页拉 GET /api/v1/feedback/config 同步。权限:operator 可改(运营维护),super 恒可; 读为只读(任意已登录 admin)。 """ from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile from app.admin.audit import write_audit from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role from app.admin.schemas.feedback_qr import FeedbackQrOut, FeedbackQrUpdate from app.core import media from app.models.admin import AdminUser from app.repositories import feedback_qr router = APIRouter( prefix="/admin/api/feedback-config", tags=["admin-feedback-config"], dependencies=[Depends(get_current_admin)], ) @router.get("", response_model=FeedbackQrOut, summary="反馈页二维码卡配置") def get_config(db: AdminDb) -> FeedbackQrOut: return FeedbackQrOut(**feedback_qr.get_config(db)) @router.patch("", response_model=FeedbackQrOut, summary="改反馈页文案/开关(带审计)") def update_config( body: FeedbackQrUpdate, request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> FeedbackQrOut: before, after = feedback_qr.update_config( db, enabled=body.enabled, title=body.title, group_name=body.group_name, subtitle=body.subtitle, admin_id=admin.id, commit=False, ) write_audit( db, admin, action="feedback_qr.update", target_type="feedback_qr", target_id=None, detail={"before": before, "after": after}, ip=get_client_ip(request), commit=False, ) db.commit() return FeedbackQrOut(**after) @router.post("/image", response_model=FeedbackQrOut, summary="上传反馈页二维码(带审计)") async def upload_image( request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, file: UploadFile = File(...), ) -> FeedbackQrOut: data = await file.read() try: url = media.save_feedback_qr(data) except media.MediaError as e: raise HTTPException(status_code=400, detail=str(e)) from e before, after = feedback_qr.set_image(db, url, admin_id=admin.id, commit=False) write_audit( db, admin, action="feedback_qr.set_image", target_type="feedback_qr", target_id=None, detail={"before": before.get("image_url"), "after": url}, ip=get_client_ip(request), commit=False, ) db.commit() media.delete_feedback_qr(before.get("image_url")) # 提交成功后再删旧图,避免新图未落库就丢旧图 return FeedbackQrOut(**after) @router.delete("/image", response_model=FeedbackQrOut, summary="移除反馈页二维码(带审计)") def delete_image( request: Request, admin: Annotated[AdminUser, Depends(require_role("operator"))], db: AdminDb, ) -> FeedbackQrOut: before, after = feedback_qr.set_image(db, None, admin_id=admin.id, commit=False) write_audit( db, admin, action="feedback_qr.delete_image", target_type="feedback_qr", target_id=None, detail={"before": before.get("image_url")}, ip=get_client_ip(request), commit=False, ) db.commit() media.delete_feedback_qr(before.get("image_url")) return FeedbackQrOut(**after)