diff --git a/backend/main.py b/backend/main.py index a9a1525..8af671e 100755 --- a/backend/main.py +++ b/backend/main.py @@ -490,15 +490,6 @@ def get_notify_config_from_db(conn) -> dict: "alert_email_to": os.getenv("ALERT_EMAIL_TO", ""), } -def refresh_notifier(conn): - """활성화된 모든 채널 중 첫 번째 채널로 notifier 모듈 업데이트""" - import notifier - cfg = get_notify_config_from_db(conn) - notifier.DISCORD_WEBHOOK_URL = cfg["discord_webhook_url"] - notifier.GMAIL_USER = cfg["gmail_user"] - notifier.GMAIL_APP_PASSWORD = cfg["gmail_app_password"] - notifier.ALERT_EMAIL_TO = cfg["alert_email_to"] - @app.get("/api/admin/notify-channels") def list_notify_channels(token=Depends(require_admin), conn=Depends(get_db)): cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) @@ -516,7 +507,6 @@ def create_notify_channel(data: NotifyChannel, token=Depends(require_admin), con """, (data.name, data.type, data.discord_webhook_url or "", data.gmail_user or "", data.gmail_app_password or "", data.alert_email_to or "", data.enabled)) conn.commit() - refresh_notifier(conn) return cur.fetchone() @app.put("/api/admin/notify-channels/{channel_id}") @@ -538,7 +528,6 @@ def update_notify_channel(channel_id: int, data: NotifyChannel, token=Depends(re """, (data.name, data.type, data.discord_webhook_url or "", data.gmail_user or "", data.alert_email_to or "", data.enabled, channel_id)) conn.commit() - refresh_notifier(conn) return cur.fetchone() @app.delete("/api/admin/notify-channels/{channel_id}") @@ -546,7 +535,6 @@ def delete_notify_channel(channel_id: int, token=Depends(require_admin), conn=De cur = conn.cursor() cur.execute("DELETE FROM notify_channels WHERE id=%s", (channel_id,)) conn.commit() - refresh_notifier(conn) return {"ok": True} # ─── 하위호환: 기존 단일 설정 API ──────────────────────── diff --git a/backend/notifier.py b/backend/notifier.py index 7fd8985..f207f2b 100755 --- a/backend/notifier.py +++ b/backend/notifier.py @@ -1,20 +1,56 @@ import os import smtplib import httpx +import asyncio +import psycopg2 +import psycopg2.extras from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime -# ── 환경변수 ────────────────────────────────────────────── +# ── DB 설정 ─────────────────────────────────────────────── +DB_CONFIG = { + "host": os.getenv("DB_HOST", "postgres-service"), + "port": int(os.getenv("DB_PORT", "5432")), + "database": os.getenv("DB_NAME", "portaldb"), + "user": os.getenv("DB_USER", "portaluser"), + "password": os.getenv("DB_PASSWORD", "portalpass"), +} + +# ── 환경변수 fallback ───────────────────────────────────── DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL", "") GMAIL_USER = os.getenv("GMAIL_USER", "") GMAIL_APP_PASSWORD = os.getenv("GMAIL_APP_PASSWORD", "") ALERT_EMAIL_TO = os.getenv("ALERT_EMAIL_TO", "") -# ── Discord ─────────────────────────────────────────────── -async def send_discord(title: str, message: str, color: int = 0xe74c3c): - if not DISCORD_WEBHOOK_URL: - print("[NOTIFIER] Discord webhook URL not set, skipping") +# ── DB에서 활성 채널 목록 조회 ──────────────────────────── +def get_active_channels(): + try: + conn = psycopg2.connect(**DB_CONFIG) + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT * FROM notify_channels WHERE enabled=TRUE ORDER BY id") + rows = cur.fetchall() + conn.close() + if rows: + return [dict(r) for r in rows] + except Exception as e: + print(f"[NOTIFIER] DB channel load failed: {e}") + + # fallback: 환경변수로 단일 채널 구성 + if DISCORD_WEBHOOK_URL or GMAIL_USER: + return [{ + "id": 0, "name": "default", "type": "both", + "discord_webhook_url": DISCORD_WEBHOOK_URL, + "gmail_user": GMAIL_USER, + "gmail_app_password": GMAIL_APP_PASSWORD, + "alert_email_to": ALERT_EMAIL_TO, + "enabled": True, + }] + return [] + +# ── Discord 단건 발송 ───────────────────────────────────── +async def _send_discord(webhook_url: str, title: str, message: str, color: int): + if not webhook_url: return payload = { "embeds": [{ @@ -27,7 +63,7 @@ async def send_discord(title: str, message: str, color: int = 0xe74c3c): } try: async with httpx.AsyncClient() as client: - res = await client.post(DISCORD_WEBHOOK_URL, json=payload, timeout=10) + res = await client.post(webhook_url, json=payload, timeout=10) if res.status_code not in (200, 204): print(f"[NOTIFIER] Discord error: {res.status_code} {res.text}") else: @@ -35,17 +71,17 @@ async def send_discord(title: str, message: str, color: int = 0xe74c3c): except Exception as e: print(f"[NOTIFIER] Discord exception: {e}") -# ── Gmail ───────────────────────────────────────────────── -def send_email(subject: str, body: str): - if not all([GMAIL_USER, GMAIL_APP_PASSWORD, ALERT_EMAIL_TO]): +# ── Gmail 단건 발송 ─────────────────────────────────────── +def _send_email(gmail_user: str, gmail_app_password: str, alert_email_to: str, + subject: str, body: str): + if not all([gmail_user, gmail_app_password, alert_email_to]): print("[NOTIFIER] Gmail config not set, skipping") return try: msg = MIMEMultipart("alternative") msg["Subject"] = f"[Web Portal] {subject}" - msg["From"] = GMAIL_USER - msg["To"] = ALERT_EMAIL_TO - + msg["From"] = gmail_user + msg["To"] = alert_email_to html = f"""