Files
bambuddy/backend/app/api/routes/bug_report.py
T
maziggy 058f74a7da Add in-app bug reporting with relay, debug log collection, and privacy controls
Floating bug report button submits issues via bambuddy.cool relay (no GitHub
  token needed locally). Collects 30s debug logs with printer push_all, sanitizes
  all sensitive data, uploads logs as files to GitHub. Screenshot upload/paste/drag
  with JPEG compression. Translated into all 7 languages. Includes 21 tests.
2026-03-04 13:33:19 +01:00

96 lines
2.9 KiB
Python

"""Bug report endpoint for submitting user bug reports to GitHub."""
import asyncio
import logging
from fastapi import APIRouter
from pydantic import BaseModel
from backend.app.api.routes.support import (
_apply_log_level,
_collect_support_info,
_get_debug_setting,
_get_recent_sanitized_logs,
_set_debug_setting,
)
from backend.app.core.database import async_session
from backend.app.services.bug_report import submit_report
from backend.app.services.printer_manager import printer_manager
router = APIRouter(prefix="/bug-report", tags=["bug-report"])
logger = logging.getLogger(__name__)
LOG_COLLECTION_SECONDS = 30
class BugReportRequest(BaseModel):
description: str
email: str | None = None
screenshot_base64: str | None = None
include_support_info: bool = True
class BugReportResponse(BaseModel):
success: bool
message: str
issue_url: str | None = None
issue_number: int | None = None
async def _collect_debug_logs() -> str:
"""Enable debug logging, push all printers, wait, then collect logs."""
# Check if debug was already enabled
async with async_session() as db:
was_debug, _ = await _get_debug_setting(db)
# Enable debug logging
if not was_debug:
async with async_session() as db:
await _set_debug_setting(db, True)
_apply_log_level(True)
logger.info("Bug report: temporarily enabled debug logging")
# Send push_all to all connected printers
for printer_id in list(printer_manager._clients.keys()):
try:
printer_manager.request_status_update(printer_id)
except Exception:
logger.debug("Failed to push_all for printer %s", printer_id)
# Wait for logs to accumulate
await asyncio.sleep(LOG_COLLECTION_SECONDS)
# Collect logs
logs = await _get_recent_sanitized_logs()
# Restore previous log level if it wasn't debug before
if not was_debug:
async with async_session() as db:
await _set_debug_setting(db, False)
_apply_log_level(False)
logger.info("Bug report: restored normal logging")
return logs
@router.post("/submit", response_model=BugReportResponse)
async def submit_bug_report(report: BugReportRequest):
"""Submit a bug report. No auth required — anyone should be able to report bugs."""
support_info = None
if report.include_support_info:
try:
support_info = await _collect_support_info()
logs = await _collect_debug_logs()
if logs:
support_info["recent_logs"] = logs
except Exception:
logger.exception("Failed to collect support info for bug report")
result = await submit_report(
description=report.description,
reporter_email=report.email,
screenshot_base64=report.screenshot_base64,
support_info=support_info,
)
return BugReportResponse(**result)