- itsm/ -> workspace/guardia-itsm/ - manager/ -> workspace/guardia-manager/ - app/ -> workspace/guardia-messenger/ - manual/ -> workspace/guardia-docs/ workspace/zioinfo-web/ unchanged. git mv preserves full commit history. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
3.5 KiB
Python
126 lines
3.5 KiB
Python
"""G-10: PWA Push 알림 구독 관리 API."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user, require_admin_role
|
|
from database import get_db
|
|
from models import PushSubscription, User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/push", tags=["push"])
|
|
|
|
|
|
class SubscribeRequest(BaseModel):
|
|
endpoint: str
|
|
p256dh: str
|
|
auth: str
|
|
user_agent: Optional[str] = None
|
|
|
|
|
|
class PushTestRequest(BaseModel):
|
|
title: str = "GUARDiA 테스트"
|
|
body: str = "푸시 알림 테스트입니다."
|
|
url: str = "/"
|
|
|
|
|
|
@router.get("/vapid-key")
|
|
async def get_vapid_public_key():
|
|
"""VAPID 공개키 반환 (프론트엔드 subscribe 시 사용)."""
|
|
key = os.getenv("VAPID_PUBLIC_KEY", "")
|
|
return {"vapid_public_key": key, "configured": bool(key)}
|
|
|
|
|
|
@router.post("/subscribe", status_code=201)
|
|
async def subscribe(
|
|
body: SubscribeRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""PWA Push 구독 등록."""
|
|
# 기존 구독 확인 (endpoint 기준)
|
|
existing = (await db.execute(
|
|
select(PushSubscription).where(PushSubscription.endpoint == body.endpoint)
|
|
)).scalars().first()
|
|
|
|
if existing:
|
|
# 갱신
|
|
existing.user_id = current_user.id
|
|
existing.p256dh = body.p256dh
|
|
existing.auth = body.auth
|
|
existing.user_agent = body.user_agent
|
|
else:
|
|
sub = PushSubscription(
|
|
user_id = current_user.id,
|
|
endpoint = body.endpoint,
|
|
p256dh = body.p256dh,
|
|
auth = body.auth,
|
|
user_agent = body.user_agent,
|
|
)
|
|
db.add(sub)
|
|
|
|
await db.commit()
|
|
return {"message": "푸시 구독이 등록되었습니다.", "user": current_user.username}
|
|
|
|
|
|
@router.delete("/subscribe", status_code=204)
|
|
async def unsubscribe(
|
|
endpoint: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""PWA Push 구독 해제."""
|
|
sub = (await db.execute(
|
|
select(PushSubscription).where(
|
|
PushSubscription.endpoint == endpoint,
|
|
PushSubscription.user_id == current_user.id,
|
|
)
|
|
)).scalars().first()
|
|
|
|
if sub:
|
|
await db.delete(sub)
|
|
await db.commit()
|
|
|
|
|
|
@router.post("/test")
|
|
async def test_push(
|
|
body: PushTestRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(require_admin_role),
|
|
):
|
|
"""테스트 푸시 전송 (ADMIN 전용 — 자신의 구독에 발송)."""
|
|
subs = (await db.execute(
|
|
select(PushSubscription).where(PushSubscription.user_id == current_user.id)
|
|
)).scalars().all()
|
|
|
|
if not subs:
|
|
raise HTTPException(404, "등록된 푸시 구독이 없습니다. 먼저 구독을 등록하세요.")
|
|
|
|
from core.push_notify import send_push
|
|
sent = 0
|
|
for sub in subs:
|
|
ok = await send_push(
|
|
subscription_info={
|
|
"endpoint": sub.endpoint,
|
|
"keys": {"p256dh": sub.p256dh, "auth": sub.auth},
|
|
},
|
|
title=body.title,
|
|
body=body.body,
|
|
url=body.url,
|
|
)
|
|
if ok:
|
|
sent += 1
|
|
|
|
return {
|
|
"message": f"테스트 푸시 전송 완료 (성공: {sent}/{len(subs)})",
|
|
"sent": sent,
|
|
"total_subs": len(subs),
|
|
}
|