"""Synology Chat webhook — outgoing webhook 수신 + 파이프라인 연결.""" from __future__ import annotations import logging import time from fastapi import APIRouter, Request from fastapi.responses import JSONResponse from config import settings from services.job_manager import job_manager from services import job_queue as jq_module from services.state_stream import state_stream from services.synology_sender import send_to_synology logger = logging.getLogger(__name__) router = APIRouter(tags=["synology"]) # 중복 요청 방지 (retry 대비) — {user_id}:{timestamp} → expire time _recent: dict[str, float] = {} DEDUP_TTL = 30.0 # 30초 내 동일 요청 무시 def _cleanup_recent(): now = time.time() expired = [k for k, v in _recent.items() if now - v > DEDUP_TTL] for k in expired: del _recent[k] @router.post("/webhook/synology") async def synology_webhook(request: Request): """Synology Chat outgoing webhook 수신.""" if not settings.synology_incoming_url: return JSONResponse(status_code=503, content={"detail": "Synology integration disabled"}) # Parse form data form = await request.form() token = form.get("token", "") text = form.get("text", "") username = form.get("username", "") user_id = form.get("user_id", "") timestamp = form.get("timestamp", "") # Token 검증 if token != settings.synology_outgoing_token: logger.warning("Invalid Synology token from %s", username) return JSONResponse(status_code=403, content={"detail": "Invalid token"}) if not text or not text.strip(): return JSONResponse(status_code=200, content={"text": "빈 메시지입니다."}) # 중복 요청 방지 _cleanup_recent() dedup_key = f"{user_id}:{timestamp}" if dedup_key in _recent: logger.info("Duplicate webhook ignored: %s", dedup_key) return JSONResponse(status_code=200, content={}) _recent[dedup_key] = time.time() # Job 생성 job = job_manager.create(text.strip()) job.callback = "synology" job.callback_meta = {"username": username, "user_id": user_id} state_stream.create(job.id) logger.info("Synology job %s from %s: %s", job.id, username, text[:50]) # "처리 중" 메시지 먼저 전송 (typing 느낌) await send_to_synology(f"🤖 분석 중... (from {username})") # 파이프라인 시작 (비동기) await jq_module.job_queue.submit(job) # 즉시 200 반환 (Synology는 빠른 응답 기대) return JSONResponse(status_code=200, content={})