intrabot/web/server.py
2026-02-24 15:15:23 +03:00

153 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/<entity>")
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)