153 lines
5.4 KiB
Python
153 lines
5.4 KiB
Python
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)
|