import json import logging import os import threading from flask import Flask, Response, jsonify, render_template, request, stream_with_context import config import database import events from intradesk import reference from intradesk import api as intradesk_api from intradesk.handler import handle_webhook log = logging.getLogger(__name__) app = Flask(__name__, template_folder=os.path.join(os.path.dirname(__file__), "templates")) # ── Webhook от Intradesk ─────────────────────────────────────────────────────── @app.route("/webhook", methods=["POST"]) def webhook(): if config.WEBHOOK_SECRET: secret = request.headers.get("X-Webhook-Secret") or request.args.get("secret", "") if secret != config.WEBHOOK_SECRET: log.warning("Webhook: unauthorized request") return jsonify({"error": "Unauthorized"}), 401 data = request.get_json(silent=True) if not data: return jsonify({"error": "Invalid JSON"}), 400 threading.Thread(target=handle_webhook, args=(data,), daemon=True).start() return jsonify({"ok": True}), 200 @app.route("/health") def health(): return jsonify({"status": "ok"}), 200 # ── Дашборд ──────────────────────────────────────────────────────────────────── @app.route("/") def dashboard(): return render_template("dashboard.html") @app.route("/stream") def stream(): q = events.subscribe() def generate(): try: while True: try: data = q.get(timeout=25) yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n" except Exception: yield 'data: {"type":"ping"}\n\n' finally: events.unsubscribe(q) return Response( stream_with_context(generate()), mimetype="text/event-stream", headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}, ) # ── API заявок ───────────────────────────────────────────────────────────────── @app.route("/api/tasks/search") def api_tasks_search(): try: number = int(request.args.get("number", 0)) task = intradesk_api.get_task(number) if task is None: return jsonify({"error": "Заявка не найдена"}), 404 return jsonify(task) except Exception as e: return jsonify({"error": str(e)}), 400 @app.route("/api/tasks/active") def api_tasks_active(): filter_expr = request.args.get( "filter", "(status ne 68045) and (executorgroup eq 243025)" ) tasks = intradesk_api.get_tasks(filter_expr, top=50) return jsonify(tasks) @app.route("/api/task/lifetime") def api_task_lifetime(): try: task_id = int(request.args.get("task_id", 0)) top = int(request.args.get("top", 20)) data = intradesk_api.get_task_lifetime(task_id, top=top) return jsonify(data) except Exception as e: return jsonify({"error": str(e)}), 400 # ── Справочники ──────────────────────────────────────────────────────────────── @app.route("/api/ref/") def api_ref(entity): items = database.get_all_ref(entity) if not items: fallback = {"statuses": reference.STATUSES, "priorities": reference.PRIORITIES} d = fallback.get(entity, {}) items = [{"id": k, "name": v} for k, v in d.items()] return jsonify(items) # ── Изменение заявки ─────────────────────────────────────────────────────────── @app.route("/api/task/preview", methods=["POST"]) def api_task_preview(): data = request.get_json() or {} number = data.get("number") blocks = data.get("blocks", {}) body = {"number": number, "blocks": blocks, "Channel": "api"} return jsonify({ "method": "PUT", "url": "https://apigw.intradesk.ru/changes/v3/tasks/?ApiKey=***", "body": body, }) @app.route("/api/task/execute", methods=["POST"]) def api_task_execute(): data = request.get_json() or {} number = data.get("number") blocks = data.get("blocks", {}) if not number: return jsonify({"success": False, "error": "Не указан номер заявки"}), 400 success = intradesk_api.update_task(int(number), blocks) return jsonify({"success": success}) def start_server(): ssl_cert = os.path.join("/app/ssl", "cert.pem") ssl_key = os.path.join("/app/ssl", "key.pem") if os.path.exists(ssl_cert) and os.path.exists(ssl_key): log.info(f"Web server + dashboard → https://0.0.0.0:{config.PORT} (SSL)") app.run(host="0.0.0.0", port=config.PORT, ssl_context=(ssl_cert, ssl_key), use_reloader=False) else: log.info(f"Web server + dashboard → http://0.0.0.0:{config.PORT} (no SSL)") app.run(host="0.0.0.0", port=config.PORT, use_reloader=False)