zioinfo-mail/workspace/zioinfo-mail/backend/signature.py
DESKTOP-TKLFCPR\ython 0ebac500f5
Some checks are pending
GUARDiA CI / Python Lint & Import Test (push) Waiting to run
GUARDiA CI / Validate Install Scripts (push) Waiting to run
GUARDiA CI / PR Validation Summary (push) Blocked by required conditions
feat(enhance-v4): APK QR 배포 / 배치SSH / 자산QR / 스마트알림 / 웹메일 주소록+서명
- ITSM: app_deploy.py (APK 업로드·QR 생성·랜딩 페이지)
- ITSM: batch_ssh.py (다중 서버 동시 SSH 실행)
- ITSM: asset_qr.py (자산 QR 태그·체크인·라벨 인쇄)
- ITSM: smart_notify.py (조건 기반 알림 규칙 엔진)
- ITSM: models.py (AppVersion/BatchSSHJob/AssetQRToken/SmartNotifyRule 등 7개 모델)
- ITSM: main.py (4개 신규 라우터 등록)
- ITSM: static/app.js (앱배포·배치SSH·자산QR·알림규칙 4개 뷰)
- ITSM: static/index.html (신규 사이드바 메뉴 4개)
- Manager: AppDistribution.tsx (APK 업로드 UI·QR 표시·버전 관리)
- Manager: NotificationRules.tsx (알림 규칙 편집기)
- Manager: App.tsx + Sidebar.tsx (신규 라우트 등록)
- Mail: contacts.py (주소록 CRUD·자동완성)
- Mail: signature.py (HTML 서명 관리)
- Mail: Contacts.tsx + SignatureEditor.tsx (프론트엔드 컴포넌트)
- Messenger: scan.tsx (자산 QR 스캔 탭)
- Messenger: _layout.tsx (QR 탭 추가)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 19:49:00 +09:00

72 lines
2.1 KiB
Python

"""
웹메일 서명 편집기
엔드포인트:
GET /api/mail/signature — 현재 서명 조회
PUT /api/mail/signature — 서명 저장 (HTML)
"""
import re
from datetime import datetime
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from auth import verify_token
from database import get_db_session as get_db
from models import MailSignature
router = APIRouter(prefix="/api/mail/signature", tags=["메일 서명"])
ALLOWED_TAGS = {'b', 'i', 'u', 'br', 'p', 'span', 'div', 'a', 'font', 'strong', 'em', 'h1', 'h2', 'h3', 'img'}
def _sanitize_html(html: str) -> str:
"""기본 HTML sanitize (script 태그 제거)."""
html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL | re.IGNORECASE)
html = re.sub(r'on\w+="[^"]*"', '', html, flags=re.IGNORECASE)
html = re.sub(r"on\w+='[^']*'", '', html, flags=re.IGNORECASE)
return html
class SignatureUpdate(BaseModel):
html_content: str
is_active: bool = True
@router.get("/")
async def get_signature(
db: AsyncSession = Depends(get_db),
username: str = Depends(verify_token),
):
row = await db.execute(select(MailSignature).where(MailSignature.username == username))
sig = row.scalar_one_or_none()
return {
"html_content": sig.html_content if sig else "",
"is_active": sig.is_active if sig else False,
}
@router.put("/")
async def update_signature(
req: SignatureUpdate,
db: AsyncSession = Depends(get_db),
username: str = Depends(verify_token),
):
clean_html = _sanitize_html(req.html_content)
row = await db.execute(select(MailSignature).where(MailSignature.username == username))
sig = row.scalar_one_or_none()
if sig:
sig.html_content = clean_html
sig.is_active = req.is_active
sig.updated_at = datetime.utcnow()
else:
sig = MailSignature(
username=username, html_content=clean_html,
is_active=req.is_active, updated_at=datetime.utcnow(),
)
db.add(sig)
await db.commit()
return {"ok": True}