bookmyservice-ums / app /models /address_model.py
MukeshKapoor25's picture
feat(profiles): add default pet/guest functionality and refactor address model
0f81784
from datetime import datetime
from typing import Optional, List, Dict, Any
from bson import ObjectId
import logging
import uuid
from app.core.nosql_client import db
logger = logging.getLogger(__name__)
class AddressModel:
"""Model for managing user delivery addresses embedded under customer documents"""
@staticmethod
async def create_address(customer_id: str, address_data: Dict[str, Any]) -> Optional[str]:
"""Create a new embedded address for a user inside customers collection"""
try:
from app.models.user_model import BookMyServiceUserModel
address_id = str(uuid.uuid4())
current_time = datetime.utcnow()
address_doc = {
"address_id": address_id, # New field for address identification
"address_line_1": address_data.get("address_line_1"),
"address_line_2": address_data.get("address_line_2", ""),
"city": address_data.get("city"),
"state": address_data.get("state"),
"postal_code": address_data.get("postal_code"),
"country": address_data.get("country", "India"),
"address_type": address_data.get("address_type", "home"), # home, work, other
"is_default": address_data.get("is_default", False),
"landmark": address_data.get("landmark", ""),
"created_at": current_time,
"updated_at": current_time,
}
# Fetch user doc
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
logger.error(f"User not found for customer_id {customer_id}")
return None
addresses = user.get("addresses", [])
# If setting default, unset any existing defaults
if address_doc.get("is_default"):
for a in addresses:
if a.get("is_default"):
a["is_default"] = False
a["updated_at"] = datetime.utcnow()
else:
# If this is the first address, set default
if len(addresses) == 0:
address_doc["is_default"] = True
addresses.append(address_doc)
update_result = await BookMyServiceUserModel.collection.update_one(
{"customer_id": customer_id},
{"$set": {"addresses": addresses}}
)
if update_result.modified_count == 0:
logger.error(f"Failed to insert embedded address for user {customer_id}")
return None
logger.info(f"Created embedded address for user {customer_id}")
return address_doc["address_id"] # Return the address_id field instead of _id
except Exception as e:
logger.error(f"Error creating embedded address for user {customer_id}: {str(e)}")
return None
@staticmethod
async def get_user_addresses(customer_id: str) -> List[Dict[str, Any]]:
"""Get all embedded addresses for a user from customers collection"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return []
addresses = user.get("addresses", [])
# Sort by created_at desc and return as-is (no _id field)
addresses.sort(key=lambda x: x.get("created_at", datetime.utcnow()), reverse=True)
return addresses
except Exception as e:
logger.error(f"Error getting embedded addresses for user {customer_id}: {str(e)}")
return []
@staticmethod
async def get_address_by_id(customer_id: str, address_id: str) -> Optional[Dict[str, Any]]:
"""Get a specific embedded address by ID for a user"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return None
addresses = user.get("addresses", [])
for a in addresses:
if a.get("address_id") == address_id:
a_copy = dict(a)
# Inject customer_id for backward-compat where used in router
a_copy["customer_id"] = customer_id
return a_copy
return None
except Exception as e:
logger.error(f"Error getting embedded address {address_id} for user {customer_id}: {str(e)}")
return None
@staticmethod
async def update_address(customer_id: str, address_id: str, update_data: Dict[str, Any]) -> bool:
"""Update an existing embedded address"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return False
addresses = user.get("addresses", [])
updated = False
for a in addresses:
if a.get("address_id") == address_id:
allowed_fields = [
"address_line_1", "address_line_2", "city", "state", "postal_code",
"country", "address_type", "is_default", "landmark"
]
for field in allowed_fields:
if field in update_data:
a[field] = update_data[field]
a["updated_at"] = datetime.utcnow()
updated = True
break
if not updated:
return False
# If setting as default, unset other defaults
if update_data.get("is_default"):
for a in addresses:
if a.get("address_id") != address_id and a.get("is_default"):
a["is_default"] = False
a["updated_at"] = datetime.utcnow()
result = await BookMyServiceUserModel.collection.update_one(
{"customer_id": customer_id},
{"$set": {"addresses": addresses}}
)
return result.modified_count > 0
except Exception as e:
logger.error(f"Error updating embedded address {address_id} for user {customer_id}: {str(e)}")
return False
@staticmethod
async def delete_address(customer_id: str, address_id: str) -> bool:
"""Delete an embedded address"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return False
addresses = user.get("addresses", [])
# Filter out by new domain id field 'address_id'
new_addresses = [a for a in addresses if a.get("address_id") != address_id]
result = await BookMyServiceUserModel.collection.update_one(
{"customer_id": customer_id},
{"$set": {"addresses": new_addresses}}
)
logger.info(f"Deleted embedded address {address_id} for user {customer_id}")
return result.modified_count > 0
except Exception as e:
logger.error(f"Error deleting embedded address {address_id} for user {customer_id}: {str(e)}")
return False
@staticmethod
async def get_default_address(customer_id: str) -> Optional[Dict[str, Any]]:
"""Get the default embedded address for a user"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return None
addresses = user.get("addresses", [])
for a in addresses:
if a.get("is_default"):
a_copy = dict(a)
a_copy["customer_id"] = customer_id
return a_copy
return None
except Exception as e:
logger.error(f"Error getting default embedded address for user {customer_id}: {str(e)}")
return None
@staticmethod
async def set_default_address(customer_id: str, address_id: str) -> bool:
"""Set an embedded address as default and unset others"""
try:
from app.models.user_model import BookMyServiceUserModel
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
if not user:
return False
addresses = user.get("addresses", [])
changed = False
for a in addresses:
if a.get("address_id") == address_id:
if not a.get("is_default"):
a["is_default"] = True
a["updated_at"] = datetime.utcnow()
changed = True
else:
if a.get("is_default"):
a["is_default"] = False
a["updated_at"] = datetime.utcnow()
changed = True
if not changed:
# Even if nothing changed, ensure persistence
pass
result = await BookMyServiceUserModel.collection.update_one(
{"customer_id": customer_id},
{"$set": {"addresses": addresses}}
)
return result.modified_count >= 0
except Exception as e:
logger.error(f"Error setting default embedded address {address_id} for user {customer_id}: {str(e)}")
return False