Date: April 10, 2026 Phase: Phase 8 (Testing & Bug Fixes) — Continued Previous Log: Day 11 (FCM Integration, Notification Action Buttons, Undo, Inactive Schedule Cleanup)
| Task | Description | Status |
|---|---|---|
| QStash Auto-Scheduling | Auto register/remove QStash cron on medication create/update/delete/toggle | ✅ Done |
| KST → UTC Conversion | Convert scheduled_time (KST) to QStash cron expression (UTC) | ✅ Done |
| QStash JWT Signature Verification | Fix webhook signature verification from HMAC to JWT | ✅ Done |
Previously, push notifications had to be triggered manually. Introduced QStash (Upstash) cron-based scheduling to automatically send push notifications at medication times.
New file: app/services/qstash_service.py
sync_schedules() — Register QStash cron for all active schedules of a medicationdelete_all_for_medication() — Remove QStash schedules on medication delete/deactivation_build_cron() — Convert scheduled_time + cycle_type to cron expression_create_schedule() / _delete_schedule() — QStash REST API callsmedication_service.py Changes
Converted sync methods to async and added QStash sync logic to each CRUD operation:
async def create(self, user_id: str, req: MedicationCreate) -> dict:
# ... save medication + schedules to DB ...
await self._qstash.sync_schedules(medication_id, user_id)
async def update(self, medication_id: str, user_id: str, req: MedicationUpdate) -> dict:
# ... soft delete old schedules + insert new ones ...
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)
# ... soft delete medication ...
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 Migration
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;