from fastapi import APIRouter, Body, Depends, Query from typing import Optional import asyncio from app.lib_general import log from app.lib_general_v3 import AccountContext, get_account_context, DelayParams from app.models.response_models import Resp_Body_Base, mk_resp from app.methods.e_novi_mailman_methods import ( test_novi_connection, test_mailman_connection, get_mailman_lists, get_novi_members, sync_novi_to_mailman, handle_novi_webhook, ) router = APIRouter() # ── Connection Tests ────────────────────────────────────────────────────── @router.get('/test_connection/novi', response_model=Resp_Body_Base) async def test_novi( account: AccountContext = Depends(get_account_context), delay: DelayParams = Depends(), ): """Verify Novi AMS API credentials stored in data_store.""" if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s) result = test_novi_connection() if result.get('ok'): return mk_resp(data=result) return mk_resp(data=result, status_code=401, status_message="Novi connection failed.") @router.get('/test_connection/mailman', response_model=Resp_Body_Base) async def test_mailman( account: AccountContext = Depends(get_account_context), delay: DelayParams = Depends(), ): """Verify Mailman 3 REST API credentials stored in data_store.""" if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s) result = test_mailman_connection() if result.get('ok'): return mk_resp(data=result) return mk_resp(data=result, status_code=401, status_message="Mailman connection failed.") # ── Inspection / Preview ────────────────────────────────────────────────── @router.get('/mailman/lists', response_model=Resp_Body_Base) async def list_mailman_lists( account: AccountContext = Depends(get_account_context), delay: DelayParams = Depends(), ): """Return all mailing lists from this Mailman 3 instance.""" if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s) data = get_mailman_lists() if data is not None: return mk_resp(data=data) return mk_resp(data=False, status_code=500, status_message="Failed to fetch Mailman lists.") @router.get('/novi/members', response_model=Resp_Body_Base) async def list_novi_members( status_filter: Optional[str] = Query(None, description="Novi membership status filter (e.g. 'Active', 'Lapsed')"), page_size: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), account: AccountContext = Depends(get_account_context), delay: DelayParams = Depends(), ): """Fetch a page of Novi AMS members. Useful for inspecting data before a full sync.""" if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s) data = get_novi_members(status_filter=status_filter, page_size=page_size, offset=offset) if data is not None: return mk_resp(data={"count": len(data), "members": data}) return mk_resp(data=False, status_code=500, status_message="Failed to fetch members from Novi.") # ── Sync ────────────────────────────────────────────────────────────────── @router.post('/sync', response_model=Resp_Body_Base) async def sync_full( list_id: str = Query(..., description="Target Mailman list ID, e.g. 'members@yourdomain.org'"), active_status: str = Query('Active', description="Novi membership status that maps to 'subscribed'"), account: AccountContext = Depends(get_account_context), delay: DelayParams = Depends(), ): """ Full sync: pull all Novi members and reconcile Mailman subscription state. Active members are subscribed; all others are unsubscribed. This is the manual / scheduled trigger — the webhook handles real-time updates. """ if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s) result = sync_novi_to_mailman(list_id=list_id, active_status=active_status) if result: return mk_resp(data=result, status_message="Novi → Mailman sync complete.") return mk_resp(data=False, status_code=500, status_message="Sync failed.") # ── Webhook ─────────────────────────────────────────────────────────────── @router.post('/webhook/novi', response_model=Resp_Body_Base, include_in_schema=True) async def novi_membership_webhook( payload: dict = Body(...), ): """ Receives Novi AMS membership webhook events and immediately updates the corresponding Mailman subscription — no auth required (Novi pushes to this endpoint). TODO: Add HMAC signature verification once Novi webhook secret is configured. """ log.info(f"Novi webhook received: EventType={payload.get('EventType')} Email={payload.get('Member', {}).get('Email')}") result = handle_novi_webhook(payload) if result: return mk_resp(data=result) return mk_resp(data=False, status_code=400, status_message="Webhook payload could not be processed.")