147 lines
4.5 KiB
Python
147 lines
4.5 KiB
Python
"""미래 준비 — 모바일 푸시알림 디바이스 등록·발송·이력."""
|
|
from __future__ import annotations
|
|
import logging
|
|
from datetime import datetime
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select, desc
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from core.auth import get_current_user, require_admin_role
|
|
from database import get_db
|
|
from models import User, PushDevice, PushLog
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/push", tags=["미래준비-푸시알림"])
|
|
|
|
|
|
class DeviceRegister(BaseModel):
|
|
token: str
|
|
platform: str = "android"
|
|
|
|
|
|
class PushSend(BaseModel):
|
|
title: str
|
|
body: str
|
|
category: str = "general"
|
|
user_ids: list[int] = []
|
|
|
|
|
|
async def _send_to_device(device: PushDevice, title: str, body: str, category: str) -> bool:
|
|
"""FCM/Expo 푸시 발송 (온프레미스: 메신저 WebSocket으로 대체)."""
|
|
try:
|
|
import httpx
|
|
async with httpx.AsyncClient(timeout=5) as c:
|
|
r = await c.post("http://127.0.0.1:9001/api/messenger/push", json={
|
|
"token": device.token,
|
|
"platform": device.platform,
|
|
"title": title,
|
|
"body": body,
|
|
"category": category,
|
|
})
|
|
return r.status_code == 200
|
|
except Exception as e:
|
|
logger.warning("푸시 발송 실패 device=%s: %s", device.id, e)
|
|
return False
|
|
|
|
|
|
@router.post("/register")
|
|
async def register_device(
|
|
req: DeviceRegister,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""디바이스 토큰 등록 (중복이면 갱신)."""
|
|
existing = await db.execute(
|
|
select(PushDevice).where(PushDevice.token == req.token).limit(1)
|
|
)
|
|
device = existing.scalar_one_or_none()
|
|
if device:
|
|
device.active = True
|
|
device.last_used_at = datetime.utcnow()
|
|
else:
|
|
device = PushDevice(
|
|
user_id=user.id, token=req.token, platform=req.platform,
|
|
registered_at=datetime.utcnow(),
|
|
)
|
|
db.add(device)
|
|
await db.commit()
|
|
await db.refresh(device)
|
|
return {"ok": True, "device_id": device.id}
|
|
|
|
|
|
@router.delete("/register/{token}")
|
|
async def unregister_device(
|
|
token: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""디바이스 토큰 비활성화."""
|
|
row = await db.execute(
|
|
select(PushDevice).where(PushDevice.token == token, PushDevice.user_id == user.id).limit(1)
|
|
)
|
|
device = row.scalar_one_or_none()
|
|
if not device:
|
|
raise HTTPException(404, "디바이스 없음")
|
|
device.active = False
|
|
await db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.post("/send")
|
|
async def send_push(
|
|
req: PushSend,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(require_admin_role),
|
|
):
|
|
"""관리자 푸시알림 발송."""
|
|
q = select(PushDevice).where(PushDevice.active == True)
|
|
if req.user_ids:
|
|
q = q.where(PushDevice.user_id.in_(req.user_ids))
|
|
rows = await db.execute(q)
|
|
devices = rows.scalars().all()
|
|
|
|
sent = 0
|
|
for device in devices:
|
|
success = await _send_to_device(device, req.title, req.body, req.category)
|
|
log = PushLog(
|
|
device_id=device.id, title=req.title, body=req.body,
|
|
category=req.category, success=success, sent_at=datetime.utcnow(),
|
|
)
|
|
db.add(log)
|
|
if success:
|
|
sent += 1
|
|
|
|
await db.commit()
|
|
return {"ok": True, "sent": sent, "total": len(devices)}
|
|
|
|
|
|
@router.get("/logs")
|
|
async def push_logs(
|
|
limit: int = 50,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
"""푸시 발송 이력."""
|
|
rows = await db.execute(
|
|
select(PushLog).order_by(desc(PushLog.sent_at)).limit(limit)
|
|
)
|
|
return [{
|
|
"id": l.id, "title": l.title, "body": l.body[:100],
|
|
"category": l.category, "success": l.success, "sent_at": l.sent_at,
|
|
} for l in rows.scalars().all()]
|
|
|
|
|
|
@router.get("/devices")
|
|
async def list_devices(
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(require_admin_role),
|
|
):
|
|
"""등록된 디바이스 목록."""
|
|
rows = await db.execute(
|
|
select(PushDevice).where(PushDevice.active == True).order_by(desc(PushDevice.registered_at))
|
|
)
|
|
return [{
|
|
"id": d.id, "user_id": d.user_id, "platform": d.platform,
|
|
"registered_at": d.registered_at, "last_used_at": d.last_used_at,
|
|
} for d in rows.scalars().all()]
|