router = APIRouter(prefix="/attachments", tags=["attachments"]) MAX_UPLOAD_BYTES = 10 * 1024 * 1024 # 10 MB per file ALLOWED_EXTENSIONS = { "text": {".txt", ".text"}, "html": {".html", ".htm"}, "pdf": {".pdf"}, "image": {".png", ".jpg", ".jpeg", ".gif", ".webp"}, "file": {".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".html", ".htm", ".csv", ".zip"}, } MIME_BY_EXT = { ".txt": "text/plain", ".text": "text/plain", ".html": "text/html", ".htm": "text/html", ".pdf": "application/pdf", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".csv": "text/csv", ".zip": "application/zip", } ===PHONES-TAIL=== @router.post("/{pid}/patch") async def patch_phone(pid: int, payload: PhonePatch, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): p = await _get_own(db, user, pid) if payload.label is not None: p.label = payload.label.strip()[:150] if payload.is_active is not None: p.is_active = bool(payload.is_active) await db.commit() await log_audit(db, user.id, "phone.patch", "company_phone", pid, {"is_active": p.is_active}) return _serialize(p) @router.post("/{pid}/delete") async def delete_phone(pid: int, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): p = await _get_own(db, user, pid) phone_value = p.phone await db.delete(p) await db.commit() await log_audit(db, user.id, "phone.delete", "company_phone", pid, {"phone": phone_value}) return {"deleted": pid} @router.post("/clear-all") async def clear_all_phones(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): rows = (await db.execute( select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id))).scalars().all() for p in rows: await db.delete(p) await db.commit() ===RESET-ALL-RET=== )).scalars().all() for r in rows: await db.delete(r) await db.commit() await log_audit(db, user.id, "settings.reset_all", "application_setting", None, {"reset": len(rows)}) return {"reset": len(rows)} ===AUDIT=== async def list_audit(action: str = Query(""), page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): """Audit log (own actions only). Secrets are redacted at write time.""" q = select(AuditLog).where(AuditLog.user_id == user.id) if action: q = q.where(AuditLog.action == action) total = (await db.execute( select(func.count()).select_from(q.subquery()))).scalar() or 0 rows = (await db.execute( q.order_by(AuditLog.id.desc()).offset((page - 1) * page_size).limit(page_size) )).scalars().all() out = [] for r in rows: out.append({ "id": r.id, "action": r.action, "entity": r.entity, "entity_id": r.entity_id, "detail": r.detail if isinstance(r.detail, dict) else {},