diff --git a/app/methods/e_novi_mailman_methods.py b/app/methods/e_novi_mailman_methods.py index f61e868..22508c5 100644 --- a/app/methods/e_novi_mailman_methods.py +++ b/app/methods/e_novi_mailman_methods.py @@ -59,11 +59,11 @@ def test_novi_connection() -> Dict: if not config: return {"ok": False, "error": "Credentials not configured."} - # TODO: Confirm the actual test endpoint for the target Novi instance. - # Common pattern: GET /api/v1/members?$top=1 with ApiKey header. + # Novi uses Basic auth with a Base64-encoded API key. + # Confirmed from IDAA Jitsi integration: Authorization: Basic {api_key} base_url = config.get('base_url', '').rstrip('/') api_key = config.get('api_key', '') - headers = {"ApiKey": api_key, "Accept": "application/json"} + headers = {"Authorization": f"Basic {api_key}", "Accept": "application/json"} try: resp = requests.get(f"{base_url}/api/v1/members", headers=headers, params={"$top": 1}, timeout=10) @@ -97,20 +97,26 @@ def get_novi_members(status_filter: Optional[str] = None, page_size: int = 500, base_url = config.get('base_url', '').rstrip('/') api_key = config.get('api_key', '') - headers = {"ApiKey": api_key, "Accept": "application/json"} - params = {"$top": page_size, "$skip": offset} + headers = {"Authorization": f"Basic {api_key}", "Accept": "application/json"} + params = {"pageSize": page_size, "offset": offset} if status_filter: - params["$filter"] = f"MembershipStatus eq '{status_filter}'" + params["membershipStatus"] = status_filter + # TODO: Confirm the bulk member list endpoint for this Novi instance. + # The IDAA Jitsi code uses /customers/{uuid} for individual lookups and + # /groups/{guid}/members for group membership. A bulk member list may be + # /members, /customers, or require a group-based approach. try: - resp = requests.get(f"{base_url}/api/v1/members", headers=headers, params=params, timeout=30) + resp = requests.get(f"{base_url}/members", headers=headers, params=params, timeout=30) if resp.status_code != 200: log.error(f"Novi API error: {resp.status_code} - {resp.text[:200]}") return None data = resp.json() - # Novi typically wraps results in a 'value' key (OData convention) - return data.get('value', data) if isinstance(data, dict) else data + # Novi may return array directly, or wrap in Results/Members key + if isinstance(data, list): + return data + return data.get('Results') or data.get('Members') or data.get('value') or [] except Exception as e: log.exception(f"Failed to fetch Novi members: {e}") return None @@ -300,11 +306,12 @@ def sync_novi_to_mailman(list_id: str, active_status: str = 'Active') -> Optiona results = {"total": len(members), "subscribed": 0, "unsubscribed": 0, "error": 0, "skipped": 0} for member in members: - # TODO: Confirm exact field names from your Novi instance - email = (member.get('Email') or member.get('email') or '').strip() - fname = member.get('FirstName') or member.get('first_name') or '' - lname = member.get('LastName') or member.get('last_name') or '' - status = member.get('MembershipStatus') or member.get('membership_status') or '' + # Field names confirmed PascalCase from Novi API (verified via IDAA Jitsi integration). + # MembershipStatus field name still needs confirmation against the bulk member endpoint. + email = (member.get('Email') or '').strip().replace(' ', '+') + fname = member.get('FirstName') or '' + lname = member.get('LastName') or '' + status = member.get('MembershipStatus') or member.get('Status') or '' if not email: results['skipped'] += 1 @@ -341,10 +348,10 @@ def handle_novi_webhook(payload: Dict) -> Optional[Dict]: """ event_type = payload.get('EventType', '') member = payload.get('Member', {}) - email = (member.get('Email') or '').strip() + email = (member.get('Email') or '').strip().replace(' ', '+') fname = member.get('FirstName', '') lname = member.get('LastName', '') - status = member.get('MembershipStatus', '') + status = member.get('MembershipStatus') or member.get('Status', '') if not email: log.warning(f"Novi webhook received with no email — skipping. Payload: {payload}")