""" Issue reporting — user-submitted bug/feedback reports. POST /api/report-issue — save an issue report locally + forward to feedback service GET /api/report-issues — list recent local reports For public installs (no Clerk auth configured), reports are also sent to https://feedback.openvoiceui.com/api/report so the dev team can see them. This is fire-and-forget — local save always succeeds regardless. """ import json import logging import os import threading from datetime import datetime from pathlib import Path import requests as http_requests from flask import Blueprint, jsonify, request from services.paths import RUNTIME_DIR logger = logging.getLogger(__name__) report_issue_bp = Blueprint('report_issue', __name__) REPORTS_DIR = RUNTIME_DIR / 'issue-reports' FEEDBACK_URL = 'https://feedback.openvoiceui.com/api/report' def _is_public_install() -> bool: """True if this is a public/Pinokio install (no Clerk auth configured).""" return not os.environ.get('CLERK_PUBLISHABLE_KEY', '').strip() def _forward_to_feedback_service(report: dict): """Fire-and-forget POST to the public feedback service.""" try: resp = http_requests.post( FEEDBACK_URL, json=report, timeout=10, headers={'Content-Type': 'application/json'}, ) if resp.ok: logger.info('Feedback forwarded to %s', FEEDBACK_URL) else: logger.warning('Feedback forward failed: %s %s', resp.status_code, resp.text[:200]) except Exception as e: logger.warning('Feedback forward error: %s', e) @report_issue_bp.route('/api/report-issue', methods=['POST']) def submit_issue(): data = request.get_json(force=True, silent=True) or {} issue_type = (data.get('type') or 'other').strip()[:50] description = (data.get('description') or '').strip()[:2000] context = data.get('context') or {} if not description: return jsonify({'error': 'Description required'}), 400 now = datetime.now() report = { 'ts': now.isoformat(), 'type': issue_type, 'description': description, 'context': context, 'ua': request.headers.get('User-Agent', ''), } # Always save locally. Never let a filesystem error bubble up as an HTML 500 # page — the frontend does res.json() and an HTML body yields the cryptic # "Unexpected token '<', "