작성일: 2026년 4월 10일 진행 단계: Phase 8 (테스트 및 버그 수정) — 계속 이전 일지: Day 11 (FCM 연동, 알림 액션 버튼, Undo 기능, 비활성 스케줄 정리)


✅ 이번 세션 진행 현황

작업 내용 상태
QStash 자동 스케줄링 약 등록/수정/삭제/토글 시 QStash cron 자동 등록/해제 ✅ 완료
KST → UTC 변환 scheduled_time(KST)을 QStash cron(UTC)으로 변환 ✅ 완료
QStash JWT 서명 검증 HMAC → JWT 방식으로 웹훅 서명 검증 수정 ✅ 완료

1. QStash 자동 스케줄링

배경

기존에는 알림 발송을 수동으로 트리거해야 했다. 약 복용 시간에 맞춰 자동으로 푸시 알림이 발송되도록 QStash(Upstash) 기반의 cron 스케줄링을 도입했다.

구현 내용

새 파일: app/services/qstash_service.py

medication_service.py 수정

기존 동기 메서드를 async로 변경하고, 각 CRUD 동작에 QStash 동기화 로직 추가:

async def create(self, user_id: str, req: MedicationCreate) -> dict:
    # ... 약 등록 + 스케줄 DB 저장 ...
    await self._qstash.sync_schedules(medication_id, user_id)

async def update(self, medication_id: str, user_id: str, req: MedicationUpdate) -> dict:
    # ... 스케줄 소프트 삭제 + 새 스케줄 삽입 ...
    await self._qstash.sync_schedules(medication_id, user_id)

async def delete(self, medication_id: str, user_id: str) -> None:
    await self._qstash.delete_all_for_medication(medication_id)
    # ... 소프트 삭제 ...

async def toggle(self, medication_id: str, user_id: str) -> dict:
    if new_state:
        await self._qstash.sync_schedules(medication_id, user_id)
    else:
        await self._qstash.delete_all_for_medication(medication_id)

DB 마이그레이션

ALTER TABLE schedules ADD COLUMN IF NOT EXISTS qstash_schedule_id TEXT DEFAULT NULL;
CREATE INDEX IF NOT EXISTS idx_schedules_qstash_id ON schedules (qstash_schedule_id)
WHERE qstash_schedule_id IS NOT NULL;

동작 흐름