"""On-prem sync endpoint — polled by TapTrack every 30s to get SMS jobs and config.""" from datetime import datetime, timezone from fastapi import APIRouter, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_, update from fastapi import Depends from app.database import get_db from app.models.license import License, LicenseStatus from app.models.school import School from app.models.sms import SmsJob, SmsJobStatus router = APIRouter(prefix="/api/sync", tags=["sync"]) @router.post("/poll") async def sync_poll( request: Request, db: AsyncSession = Depends(get_db), ): """ Called by on-prem TapTrack every 30s. Returns pending SMS jobs and current config (sender_name, credits, feature flags). Body: { license_key: str, report_sent_ids: [str] } (completed job IDs to mark as sent) """ body = await request.json() key = body.get("license_key", "") sent_ids = body.get("report_sent_ids", []) lic = (await db.execute(select(License).where(License.key == key))).scalar_one_or_none() if not lic or lic.status == LicenseStatus.revoked: raise HTTPException(403, "Invalid or revoked license") school = (await db.execute(select(School).where(School.id == lic.school_id))).scalar_one_or_none() if not school: raise HTTPException(404) # Mark completed jobs if sent_ids: await db.execute( update(SmsJob) .where(and_(SmsJob.id.in_(sent_ids), SmsJob.school_id == school.id)) .values(status=SmsJobStatus.sent, sent_at=datetime.now(timezone.utc)) ) # Get pending jobs (max 50 per poll) pending_jobs = (await db.execute( select(SmsJob) .where(and_(SmsJob.school_id == school.id, SmsJob.status == SmsJobStatus.pending)) .limit(50) )).scalars().all() # Mark as processing job_ids = [j.id for j in pending_jobs] if job_ids: await db.execute( update(SmsJob) .where(SmsJob.id.in_(job_ids)) .values(status=SmsJobStatus.processing) ) # Update last seen lic.last_validated_at = datetime.now(timezone.utc) lic.last_seen_ip = request.client.host if request.client else None await db.commit() return { "sms_jobs": [ {"id": j.id, "recipient_phone": j.recipient_phone, "message": j.message, "sender_name": j.sender_name} for j in pending_jobs ], "config": { "sms_sender_name": school.sms_sender_name, "sms_credits": float(school.sms_credits), "school_status": school.status.value, }, }