239 lines
6.5 KiB
Python
239 lines
6.5 KiB
Python
# ===================================================================
|
|
# apps/webhook/app.py
|
|
# 웹훅 수신 서버 애플리케이션
|
|
# ===================================================================
|
|
# Notion 등 외부 서비스의 웹훅을 수신하고 처리합니다.
|
|
# Mattermost 등으로 알림을 발송합니다.
|
|
# ===================================================================
|
|
"""
|
|
웹훅 수신 서버 애플리케이션
|
|
|
|
Notion 등 외부 서비스의 웹훅을 수신하고
|
|
Mattermost 등으로 알림을 발송합니다.
|
|
|
|
사용 예시:
|
|
from apps.webhook.app import create_app
|
|
|
|
app = create_app()
|
|
app.run()
|
|
"""
|
|
|
|
from flask import Flask, Blueprint, jsonify, request
|
|
from werkzeug.exceptions import HTTPException
|
|
|
|
from core.config import get_config
|
|
from core.logging_utils import get_logger, setup_logging
|
|
from services.notification import NotionWebhookHandler, MattermostNotifier
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
# Blueprint 생성
|
|
webhook_bp = Blueprint('webhook', __name__, url_prefix='/webhook')
|
|
|
|
|
|
@webhook_bp.route('/health', methods=['GET'])
|
|
def health_check():
|
|
"""헬스 체크"""
|
|
return jsonify({
|
|
'status': 'healthy',
|
|
'service': 'webhook-receiver'
|
|
})
|
|
|
|
|
|
@webhook_bp.route('/notion', methods=['POST'])
|
|
def handle_notion_webhook():
|
|
"""
|
|
Notion 웹훅 수신 엔드포인트
|
|
|
|
Notion에서 발생한 이벤트를 수신하고
|
|
Mattermost로 알림을 발송합니다.
|
|
|
|
Returns:
|
|
처리 결과 JSON
|
|
"""
|
|
# JSON 데이터 파싱
|
|
try:
|
|
event_data = request.get_json()
|
|
except Exception as e:
|
|
logger.error(f"JSON 파싱 실패: {e}")
|
|
return jsonify({'error': 'Invalid JSON'}), 400
|
|
|
|
if not event_data:
|
|
return jsonify({'error': 'Empty request body'}), 400
|
|
|
|
# 이벤트 처리
|
|
try:
|
|
handler = NotionWebhookHandler()
|
|
message = handler.handle_event(event_data)
|
|
|
|
if message:
|
|
# Mattermost로 알림 발송
|
|
notifier = MattermostNotifier.from_config()
|
|
sent = notifier.send_message(message)
|
|
|
|
if sent:
|
|
logger.info("Notion 이벤트 처리 및 알림 발송 완료")
|
|
return jsonify({
|
|
'status': 'success',
|
|
'message_sent': True
|
|
})
|
|
else:
|
|
logger.warning("알림 발송 실패")
|
|
return jsonify({
|
|
'status': 'partial',
|
|
'message_sent': False,
|
|
'reason': 'Message delivery failed'
|
|
})
|
|
else:
|
|
logger.info("이벤트 무시됨 (필터 조건 불충족)")
|
|
return jsonify({
|
|
'status': 'ignored',
|
|
'reason': 'Event filtered out'
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"이벤트 처리 실패: {e}")
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@webhook_bp.route('/test', methods=['POST'])
|
|
def test_webhook():
|
|
"""
|
|
테스트 웹훅 엔드포인트
|
|
|
|
수신된 데이터를 그대로 반환합니다.
|
|
디버깅 용도로 사용합니다.
|
|
"""
|
|
data = request.get_json() or {}
|
|
|
|
logger.info(f"테스트 웹훅 수신: {data}")
|
|
|
|
return jsonify({
|
|
'status': 'received',
|
|
'data': data,
|
|
'headers': dict(request.headers)
|
|
})
|
|
|
|
|
|
@webhook_bp.route('/notify', methods=['POST'])
|
|
def send_notification():
|
|
"""
|
|
알림 발송 엔드포인트
|
|
|
|
직접 메시지를 발송할 수 있는 엔드포인트입니다.
|
|
|
|
Request Body:
|
|
message: 발송할 메시지 (필수)
|
|
platform: 발송 플랫폼 (mattermost, telegram, synology)
|
|
channel_id: 채널 ID (선택)
|
|
|
|
Returns:
|
|
발송 결과 JSON
|
|
"""
|
|
try:
|
|
data = request.get_json()
|
|
except Exception:
|
|
return jsonify({'error': 'Invalid JSON'}), 400
|
|
|
|
if not data or 'message' not in data:
|
|
return jsonify({'error': 'Message is required'}), 400
|
|
|
|
message = data['message']
|
|
platform = data.get('platform', 'mattermost')
|
|
channel_id = data.get('channel_id')
|
|
|
|
try:
|
|
if platform == 'mattermost':
|
|
notifier = MattermostNotifier.from_config()
|
|
success = notifier.send_message(message, channel_id=channel_id)
|
|
else:
|
|
# 다른 플랫폼은 MessageSender 사용
|
|
from core.message_sender import MessageSender
|
|
sender = MessageSender.from_config()
|
|
success = sender.send(message, platforms=[platform])
|
|
|
|
return jsonify({
|
|
'status': 'success' if success else 'failed',
|
|
'platform': platform
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"알림 발송 실패: {e}")
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@webhook_bp.errorhandler(HTTPException)
|
|
def handle_exception(e):
|
|
"""HTTP 예외 핸들러"""
|
|
return jsonify({
|
|
'error': e.description,
|
|
'status_code': e.code
|
|
}), e.code
|
|
|
|
|
|
def create_app(config_override: dict = None) -> Flask:
|
|
"""
|
|
Flask 애플리케이션 팩토리
|
|
|
|
Args:
|
|
config_override: 설정 덮어쓰기 딕셔너리
|
|
|
|
Returns:
|
|
Flask 앱 인스턴스
|
|
"""
|
|
app = Flask(__name__)
|
|
|
|
# 설정 로드
|
|
config = get_config()
|
|
|
|
app.config['SECRET_KEY'] = config.flask.get('secret_key', 'dev-secret')
|
|
app.config['JSON_AS_ASCII'] = False
|
|
|
|
if config_override:
|
|
app.config.update(config_override)
|
|
|
|
# 로깅 설정
|
|
setup_logging('apps.webhook', level=config.log_level)
|
|
|
|
# Blueprint 등록
|
|
app.register_blueprint(webhook_bp)
|
|
|
|
# 루트 엔드포인트
|
|
@app.route('/')
|
|
def index():
|
|
return jsonify({
|
|
'name': 'FGTools Webhook Receiver',
|
|
'version': '1.0.0',
|
|
'endpoints': [
|
|
'/webhook/health',
|
|
'/webhook/notion',
|
|
'/webhook/test',
|
|
'/webhook/notify',
|
|
]
|
|
})
|
|
|
|
logger.info("Webhook 앱 초기화 완료")
|
|
return app
|
|
|
|
|
|
def run_server(host: str = '0.0.0.0', port: int = 5002, debug: bool = False):
|
|
"""
|
|
개발 서버 실행
|
|
|
|
Args:
|
|
host: 바인딩 호스트
|
|
port: 포트 번호
|
|
debug: 디버그 모드
|
|
"""
|
|
app = create_app()
|
|
app.run(host=host, port=port, debug=debug)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
config = get_config()
|
|
run_server(
|
|
host=config.flask.get('host', '0.0.0.0'),
|
|
port=5002,
|
|
debug=config.debug
|
|
)
|