invy/backend/google_contacts.py

176 lines
5.9 KiB
Python

import httpx
from sqlalchemy.orm import Session
from uuid import UUID
import models
import re
def normalize_phone_number(phone: str) -> str:
"""
Convert phone numbers from +972 format to Israeli 0 format
Examples:
+972501234567 -> 0501234567
+972-50-123-4567 -> 0501234567
972501234567 -> 0501234567
"""
if not phone:
return phone
# Remove all non-digit characters except +
cleaned = re.sub(r'[^\d+]', '', phone)
# Handle +972 format
if cleaned.startswith('+972'):
# Remove +972 and add 0 prefix
return '0' + cleaned[4:]
elif cleaned.startswith('972'):
# Remove 972 and add 0 prefix
return '0' + cleaned[3:]
# If already starts with 0, return as is
if cleaned.startswith('0'):
return cleaned
# If it's a 9-digit number (Israeli mobile without prefix), add 0
if len(cleaned) == 9 and cleaned[0] in '5789':
return '0' + cleaned
return cleaned
async def import_contacts_from_google(
access_token: str,
db: Session,
owner_email: str = None,
added_by_user_id: str = None,
event_id: str = None
) -> int:
"""
Import contacts from Google People API
Args:
access_token: OAuth 2.0 access token from Google
db: Database session
owner_email: Email of the account importing (stored as owner in DB)
added_by_user_id: UUID of the user adding these contacts (required for DB)
event_id: Event ID to scope import to (required)
Returns:
Number of contacts imported
"""
from uuid import UUID
# event_id and added_by_user_id are required
if not event_id:
raise ValueError("event_id is required for contact imports")
if not added_by_user_id:
raise ValueError("added_by_user_id is required for contact imports")
# Convert to UUID
event_uuid = UUID(event_id)
user_uuid = UUID(added_by_user_id)
headers = {
"Authorization": f"Bearer {access_token}"
}
# Google People API endpoint
url = "https://people.googleapis.com/v1/people/me/connections"
params = {
"personFields": "names,phoneNumbers,emailAddresses",
"pageSize": 1000
}
imported_count = 0
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params)
if response.status_code != 200:
# Try to parse error details
try:
error_data = response.json()
if 'error' in error_data:
error_info = error_data['error']
error_code = error_info.get('code')
error_message = error_info.get('message')
error_status = error_info.get('status')
if error_code == 403 or error_status == 'PERMISSION_DENIED':
raise Exception(
f"Google People API is not enabled or you don't have permission. "
f"Enable the People API in Google Cloud Console."
)
else:
raise Exception(f"Google API Error: {error_status} - {error_message}")
except ValueError:
raise Exception(f"Failed to fetch contacts: {response.text}")
data = response.json()
connections = data.get("connections", [])
for connection in connections:
# Extract name
names = connection.get("names", [])
if not names:
continue
name = names[0]
first_name = name.get("givenName", "")
last_name = name.get("familyName", "")
if not first_name and not last_name:
continue
# Extract email
emails = connection.get("emailAddresses", [])
email = emails[0].get("value") if emails else None
# Extract phone number
phones = connection.get("phoneNumbers", [])
phone_number = phones[0].get("value") if phones else None
# Normalize phone number to Israeli format (0...)
if phone_number:
phone_number = normalize_phone_number(phone_number)
# Check if contact already exists by email OR phone number
existing = None
if email:
existing = db.query(models.Guest).filter(
models.Guest.event_id == event_uuid,
models.Guest.email == email
).first()
if not existing and phone_number:
existing = db.query(models.Guest).filter(
models.Guest.event_id == event_uuid,
models.Guest.phone_number == phone_number
).first()
if existing:
# Contact exists - update owner if needed
if existing.owner_email != owner_email:
existing.owner_email = owner_email
db.add(existing)
else:
# Create new guest
guest_data = {
"first_name": first_name or "Unknown",
"last_name": last_name or "",
"email": email,
"phone_number": phone_number,
"phone": phone_number, # Also set old phone column for backward compat
"rsvp_status": "invited",
"owner_email": owner_email,
"source": "google",
"event_id": event_uuid,
"added_by_user_id": user_uuid
}
guest = models.Guest(**guest_data)
db.add(guest)
imported_count += 1
db.commit()
return imported_count