"""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), }