Spaces:
Sleeping
Sleeping
Commit ·
0f81784
1
Parent(s): 3866b57
feat(profiles): add default pet/guest functionality and refactor address model
Browse filesImplement default pet and guest selection with new endpoints and schema fields
Refactor address model to use embedded documents under customer
Remove contact fields from address schema and simplify validation
Update all related routers and models to support new embedded structure
- app/models/address_model.py +177 -109
- app/models/guest_model.py +205 -157
- app/models/pet_model.py +199 -154
- app/routers/address_router.py +21 -34
- app/routers/guest_router.py +72 -7
- app/routers/pet_router.py +72 -7
- app/schemas/address_schema.py +0 -18
- app/schemas/guest_schema.py +8 -1
- app/schemas/pet_schema.py +7 -1
app/models/address_model.py
CHANGED
|
@@ -2,22 +2,26 @@ from datetime import datetime
|
|
| 2 |
from typing import Optional, List, Dict, Any
|
| 3 |
from bson import ObjectId
|
| 4 |
import logging
|
|
|
|
| 5 |
|
| 6 |
from app.core.nosql_client import db
|
| 7 |
|
| 8 |
logger = logging.getLogger(__name__)
|
| 9 |
|
| 10 |
class AddressModel:
|
| 11 |
-
"""Model for managing user delivery addresses"""
|
| 12 |
-
|
| 13 |
-
collection = db["user_addresses"]
|
| 14 |
-
|
| 15 |
@staticmethod
|
| 16 |
async def create_address(customer_id: str, address_data: Dict[str, Any]) -> Optional[str]:
|
| 17 |
-
"""Create a new address for a user"""
|
| 18 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
address_doc = {
|
| 20 |
-
"
|
| 21 |
"address_line_1": address_data.get("address_line_1"),
|
| 22 |
"address_line_2": address_data.get("address_line_2", ""),
|
| 23 |
"city": address_data.get("city"),
|
|
@@ -27,149 +31,213 @@ class AddressModel:
|
|
| 27 |
"address_type": address_data.get("address_type", "home"), # home, work, other
|
| 28 |
"is_default": address_data.get("is_default", False),
|
| 29 |
"landmark": address_data.get("landmark", ""),
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
"created_at": datetime.utcnow(),
|
| 33 |
-
"updated_at": datetime.utcnow()
|
| 34 |
}
|
| 35 |
-
|
| 36 |
-
#
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
except Exception as e:
|
| 48 |
-
logger.error(f"Error creating address for user {customer_id}: {str(e)}")
|
| 49 |
return None
|
| 50 |
|
| 51 |
@staticmethod
|
| 52 |
async def get_user_addresses(customer_id: str) -> List[Dict[str, Any]]:
|
| 53 |
-
"""Get all addresses for a user"""
|
| 54 |
try:
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
| 62 |
return addresses
|
| 63 |
-
|
| 64 |
except Exception as e:
|
| 65 |
-
logger.error(f"Error getting addresses for user {customer_id}: {str(e)}")
|
| 66 |
return []
|
| 67 |
|
| 68 |
@staticmethod
|
| 69 |
async def get_address_by_id(customer_id: str, address_id: str) -> Optional[Dict[str, Any]]:
|
| 70 |
-
"""Get a specific address by ID for a user"""
|
| 71 |
try:
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
except Exception as e:
|
| 83 |
-
logger.error(f"Error getting address {address_id} for user {customer_id}: {str(e)}")
|
| 84 |
return None
|
| 85 |
|
| 86 |
@staticmethod
|
| 87 |
async def update_address(customer_id: str, address_id: str, update_data: Dict[str, Any]) -> bool:
|
| 88 |
-
"""Update an existing address"""
|
| 89 |
try:
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
]
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
)
|
| 114 |
-
|
| 115 |
return result.modified_count > 0
|
| 116 |
-
|
| 117 |
except Exception as e:
|
| 118 |
-
logger.error(f"Error updating address {address_id} for user {customer_id}: {str(e)}")
|
| 119 |
return False
|
| 120 |
|
| 121 |
@staticmethod
|
| 122 |
async def delete_address(customer_id: str, address_id: str) -> bool:
|
| 123 |
-
"""Delete an address"""
|
| 124 |
try:
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
except Exception as e:
|
| 134 |
-
logger.error(f"Error deleting address {address_id} for user {customer_id}: {str(e)}")
|
| 135 |
return False
|
| 136 |
|
| 137 |
@staticmethod
|
| 138 |
async def get_default_address(customer_id: str) -> Optional[Dict[str, Any]]:
|
| 139 |
-
"""Get the default address for a user"""
|
| 140 |
try:
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
| 151 |
except Exception as e:
|
| 152 |
-
logger.error(f"Error getting default address for user {customer_id}: {str(e)}")
|
| 153 |
return None
|
| 154 |
|
| 155 |
@staticmethod
|
| 156 |
async def set_default_address(customer_id: str, address_id: str) -> bool:
|
| 157 |
-
"""Set an address as default"""
|
| 158 |
try:
|
| 159 |
-
|
| 160 |
-
await
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
)
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
except Exception as e:
|
| 174 |
-
logger.error(f"Error setting default address {address_id} for user {customer_id}: {str(e)}")
|
| 175 |
return False
|
|
|
|
| 2 |
from typing import Optional, List, Dict, Any
|
| 3 |
from bson import ObjectId
|
| 4 |
import logging
|
| 5 |
+
import uuid
|
| 6 |
|
| 7 |
from app.core.nosql_client import db
|
| 8 |
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class AddressModel:
|
| 12 |
+
"""Model for managing user delivery addresses embedded under customer documents"""
|
| 13 |
+
|
|
|
|
|
|
|
| 14 |
@staticmethod
|
| 15 |
async def create_address(customer_id: str, address_data: Dict[str, Any]) -> Optional[str]:
|
| 16 |
+
"""Create a new embedded address for a user inside customers collection"""
|
| 17 |
try:
|
| 18 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 19 |
+
|
| 20 |
+
address_id = str(uuid.uuid4())
|
| 21 |
+
current_time = datetime.utcnow()
|
| 22 |
+
|
| 23 |
address_doc = {
|
| 24 |
+
"address_id": address_id, # New field for address identification
|
| 25 |
"address_line_1": address_data.get("address_line_1"),
|
| 26 |
"address_line_2": address_data.get("address_line_2", ""),
|
| 27 |
"city": address_data.get("city"),
|
|
|
|
| 31 |
"address_type": address_data.get("address_type", "home"), # home, work, other
|
| 32 |
"is_default": address_data.get("is_default", False),
|
| 33 |
"landmark": address_data.get("landmark", ""),
|
| 34 |
+
"created_at": current_time,
|
| 35 |
+
"updated_at": current_time,
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
+
|
| 38 |
+
# Fetch user doc
|
| 39 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 40 |
+
if not user:
|
| 41 |
+
logger.error(f"User not found for customer_id {customer_id}")
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
addresses = user.get("addresses", [])
|
| 45 |
+
|
| 46 |
+
# If setting default, unset any existing defaults
|
| 47 |
+
if address_doc.get("is_default"):
|
| 48 |
+
for a in addresses:
|
| 49 |
+
if a.get("is_default"):
|
| 50 |
+
a["is_default"] = False
|
| 51 |
+
a["updated_at"] = datetime.utcnow()
|
| 52 |
+
else:
|
| 53 |
+
# If this is the first address, set default
|
| 54 |
+
if len(addresses) == 0:
|
| 55 |
+
address_doc["is_default"] = True
|
| 56 |
+
|
| 57 |
+
addresses.append(address_doc)
|
| 58 |
+
|
| 59 |
+
update_result = await BookMyServiceUserModel.collection.update_one(
|
| 60 |
+
{"customer_id": customer_id},
|
| 61 |
+
{"$set": {"addresses": addresses}}
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if update_result.modified_count == 0:
|
| 65 |
+
logger.error(f"Failed to insert embedded address for user {customer_id}")
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
logger.info(f"Created embedded address for user {customer_id}")
|
| 69 |
+
return address_doc["address_id"] # Return the address_id field instead of _id
|
| 70 |
+
|
| 71 |
except Exception as e:
|
| 72 |
+
logger.error(f"Error creating embedded address for user {customer_id}: {str(e)}")
|
| 73 |
return None
|
| 74 |
|
| 75 |
@staticmethod
|
| 76 |
async def get_user_addresses(customer_id: str) -> List[Dict[str, Any]]:
|
| 77 |
+
"""Get all embedded addresses for a user from customers collection"""
|
| 78 |
try:
|
| 79 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 80 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 81 |
+
if not user:
|
| 82 |
+
return []
|
| 83 |
+
|
| 84 |
+
addresses = user.get("addresses", [])
|
| 85 |
+
# Sort by created_at desc and return as-is (no _id field)
|
| 86 |
+
addresses.sort(key=lambda x: x.get("created_at", datetime.utcnow()), reverse=True)
|
| 87 |
return addresses
|
| 88 |
+
|
| 89 |
except Exception as e:
|
| 90 |
+
logger.error(f"Error getting embedded addresses for user {customer_id}: {str(e)}")
|
| 91 |
return []
|
| 92 |
|
| 93 |
@staticmethod
|
| 94 |
async def get_address_by_id(customer_id: str, address_id: str) -> Optional[Dict[str, Any]]:
|
| 95 |
+
"""Get a specific embedded address by ID for a user"""
|
| 96 |
try:
|
| 97 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 98 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 99 |
+
if not user:
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
addresses = user.get("addresses", [])
|
| 103 |
+
|
| 104 |
+
for a in addresses:
|
| 105 |
+
if a.get("address_id") == address_id:
|
| 106 |
+
a_copy = dict(a)
|
| 107 |
+
# Inject customer_id for backward-compat where used in router
|
| 108 |
+
a_copy["customer_id"] = customer_id
|
| 109 |
+
return a_copy
|
| 110 |
+
return None
|
| 111 |
+
|
| 112 |
except Exception as e:
|
| 113 |
+
logger.error(f"Error getting embedded address {address_id} for user {customer_id}: {str(e)}")
|
| 114 |
return None
|
| 115 |
|
| 116 |
@staticmethod
|
| 117 |
async def update_address(customer_id: str, address_id: str, update_data: Dict[str, Any]) -> bool:
|
| 118 |
+
"""Update an existing embedded address"""
|
| 119 |
try:
|
| 120 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 121 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 122 |
+
if not user:
|
| 123 |
+
return False
|
| 124 |
+
|
| 125 |
+
addresses = user.get("addresses", [])
|
| 126 |
+
|
| 127 |
+
updated = False
|
| 128 |
+
for a in addresses:
|
| 129 |
+
if a.get("address_id") == address_id:
|
| 130 |
+
allowed_fields = [
|
| 131 |
+
"address_line_1", "address_line_2", "city", "state", "postal_code",
|
| 132 |
+
"country", "address_type", "is_default", "landmark"
|
| 133 |
+
]
|
| 134 |
+
for field in allowed_fields:
|
| 135 |
+
if field in update_data:
|
| 136 |
+
a[field] = update_data[field]
|
| 137 |
+
a["updated_at"] = datetime.utcnow()
|
| 138 |
+
updated = True
|
| 139 |
+
break
|
| 140 |
+
|
| 141 |
+
if not updated:
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
# If setting as default, unset other defaults
|
| 145 |
+
if update_data.get("is_default"):
|
| 146 |
+
for a in addresses:
|
| 147 |
+
if a.get("address_id") != address_id and a.get("is_default"):
|
| 148 |
+
a["is_default"] = False
|
| 149 |
+
a["updated_at"] = datetime.utcnow()
|
| 150 |
+
|
| 151 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 152 |
+
{"customer_id": customer_id},
|
| 153 |
+
{"$set": {"addresses": addresses}}
|
| 154 |
)
|
|
|
|
| 155 |
return result.modified_count > 0
|
| 156 |
+
|
| 157 |
except Exception as e:
|
| 158 |
+
logger.error(f"Error updating embedded address {address_id} for user {customer_id}: {str(e)}")
|
| 159 |
return False
|
| 160 |
|
| 161 |
@staticmethod
|
| 162 |
async def delete_address(customer_id: str, address_id: str) -> bool:
|
| 163 |
+
"""Delete an embedded address"""
|
| 164 |
try:
|
| 165 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 166 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 167 |
+
if not user:
|
| 168 |
+
return False
|
| 169 |
+
|
| 170 |
+
addresses = user.get("addresses", [])
|
| 171 |
+
# Filter out by new domain id field 'address_id'
|
| 172 |
+
new_addresses = [a for a in addresses if a.get("address_id") != address_id]
|
| 173 |
+
|
| 174 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 175 |
+
{"customer_id": customer_id},
|
| 176 |
+
{"$set": {"addresses": new_addresses}}
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
logger.info(f"Deleted embedded address {address_id} for user {customer_id}")
|
| 180 |
+
return result.modified_count > 0
|
| 181 |
+
|
| 182 |
except Exception as e:
|
| 183 |
+
logger.error(f"Error deleting embedded address {address_id} for user {customer_id}: {str(e)}")
|
| 184 |
return False
|
| 185 |
|
| 186 |
@staticmethod
|
| 187 |
async def get_default_address(customer_id: str) -> Optional[Dict[str, Any]]:
|
| 188 |
+
"""Get the default embedded address for a user"""
|
| 189 |
try:
|
| 190 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 191 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 192 |
+
if not user:
|
| 193 |
+
return None
|
| 194 |
+
|
| 195 |
+
addresses = user.get("addresses", [])
|
| 196 |
+
for a in addresses:
|
| 197 |
+
if a.get("is_default"):
|
| 198 |
+
a_copy = dict(a)
|
| 199 |
+
a_copy["customer_id"] = customer_id
|
| 200 |
+
return a_copy
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
except Exception as e:
|
| 204 |
+
logger.error(f"Error getting default embedded address for user {customer_id}: {str(e)}")
|
| 205 |
return None
|
| 206 |
|
| 207 |
@staticmethod
|
| 208 |
async def set_default_address(customer_id: str, address_id: str) -> bool:
|
| 209 |
+
"""Set an embedded address as default and unset others"""
|
| 210 |
try:
|
| 211 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 212 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 213 |
+
if not user:
|
| 214 |
+
return False
|
| 215 |
+
|
| 216 |
+
addresses = user.get("addresses", [])
|
| 217 |
+
|
| 218 |
+
changed = False
|
| 219 |
+
for a in addresses:
|
| 220 |
+
if a.get("address_id") == address_id:
|
| 221 |
+
if not a.get("is_default"):
|
| 222 |
+
a["is_default"] = True
|
| 223 |
+
a["updated_at"] = datetime.utcnow()
|
| 224 |
+
changed = True
|
| 225 |
+
else:
|
| 226 |
+
if a.get("is_default"):
|
| 227 |
+
a["is_default"] = False
|
| 228 |
+
a["updated_at"] = datetime.utcnow()
|
| 229 |
+
changed = True
|
| 230 |
+
|
| 231 |
+
if not changed:
|
| 232 |
+
# Even if nothing changed, ensure persistence
|
| 233 |
+
pass
|
| 234 |
+
|
| 235 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 236 |
+
{"customer_id": customer_id},
|
| 237 |
+
{"$set": {"addresses": addresses}}
|
| 238 |
)
|
| 239 |
+
return result.modified_count >= 0
|
| 240 |
+
|
|
|
|
| 241 |
except Exception as e:
|
| 242 |
+
logger.error(f"Error setting default embedded address {address_id} for user {customer_id}: {str(e)}")
|
| 243 |
return False
|
app/models/guest_model.py
CHANGED
|
@@ -9,202 +9,203 @@ from app.utils.db import prepare_for_db
|
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class GuestModel:
|
| 12 |
-
"""Model for managing guest profiles
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
-
async def create_guest(customer_id: str,guest_data:dict) -> Optional[str]:
|
| 16 |
-
"""
|
| 17 |
-
Create a new guest profile for a user.
|
| 18 |
-
|
| 19 |
-
Args:
|
| 20 |
-
customer_id: ID of the user creating the guest profile
|
| 21 |
-
first_name: Guest's first name
|
| 22 |
-
last_name: Guest's last name
|
| 23 |
-
email: Guest's email address
|
| 24 |
-
phone_number: Guest's phone number
|
| 25 |
-
gender: Guest's gender
|
| 26 |
-
date_of_birth: Guest's date of birth
|
| 27 |
-
relationship: Relationship to the user
|
| 28 |
-
notes: Additional notes about the guest
|
| 29 |
-
|
| 30 |
-
Returns:
|
| 31 |
-
Guest ID if successful, None otherwise
|
| 32 |
-
"""
|
| 33 |
try:
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
guest_id = str(uuid.uuid4())
|
| 37 |
current_time = datetime.utcnow()
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
guest_data = {
|
| 41 |
"guest_id": guest_id,
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
"
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
"created_at": current_time,
|
| 52 |
-
"updated_at": current_time
|
| 53 |
}
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
logger.info(f"Guest created successfully: {guest_id} for user: {customer_id}")
|
| 61 |
return guest_id
|
| 62 |
else:
|
| 63 |
-
logger.
|
| 64 |
-
return
|
| 65 |
-
|
| 66 |
except Exception as e:
|
| 67 |
-
logger.error(f"Error creating guest for user {customer_id}: {str(e)}")
|
| 68 |
return None
|
| 69 |
|
| 70 |
@staticmethod
|
| 71 |
async def get_user_guests(customer_id: str) -> List[Dict[str, Any]]:
|
| 72 |
-
"""
|
| 73 |
-
Get all guests for a specific user.
|
| 74 |
-
|
| 75 |
-
Args:
|
| 76 |
-
customer_id: ID of the user
|
| 77 |
-
|
| 78 |
-
Returns:
|
| 79 |
-
List of guest documents
|
| 80 |
-
"""
|
| 81 |
try:
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
logger.info(f"Retrieved {len(guests)} guests for user: {customer_id}")
|
| 92 |
return guests
|
| 93 |
-
|
| 94 |
except Exception as e:
|
| 95 |
-
logger.error(f"Error getting guests for user {customer_id}: {str(e)}")
|
| 96 |
return []
|
| 97 |
|
| 98 |
@staticmethod
|
| 99 |
-
async def get_guest_by_id(guest_id: str) -> Optional[Dict[str, Any]]:
|
| 100 |
-
"""
|
| 101 |
-
Get a specific guest by ID.
|
| 102 |
-
|
| 103 |
-
Args:
|
| 104 |
-
guest_id: ID of the guest
|
| 105 |
-
|
| 106 |
-
Returns:
|
| 107 |
-
Guest document if found, None otherwise
|
| 108 |
-
"""
|
| 109 |
try:
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
if guest:
|
| 115 |
-
guest.pop("_id", None)
|
| 116 |
-
logger.info(f"Guest found: {guest_id}")
|
| 117 |
-
return guest
|
| 118 |
-
else:
|
| 119 |
-
logger.warning(f"Guest not found: {guest_id}")
|
| 120 |
return None
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
except Exception as e:
|
| 123 |
-
logger.error(f"Error getting guest {guest_id}: {str(e)}")
|
| 124 |
return None
|
| 125 |
|
| 126 |
@staticmethod
|
| 127 |
-
async def update_guest(guest_id: str, update_fields: Dict[str, Any]) -> bool:
|
| 128 |
-
"""
|
| 129 |
-
Update a guest's information.
|
| 130 |
-
|
| 131 |
-
Args:
|
| 132 |
-
guest_id: ID of the guest to update
|
| 133 |
-
update_fields: Dictionary of fields to update
|
| 134 |
-
|
| 135 |
-
Returns:
|
| 136 |
-
True if successful, False otherwise
|
| 137 |
-
"""
|
| 138 |
try:
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
)
|
| 150 |
-
|
| 151 |
if result.modified_count > 0:
|
| 152 |
-
logger.info(f"
|
| 153 |
return True
|
| 154 |
else:
|
| 155 |
-
logger.
|
| 156 |
-
return
|
| 157 |
-
|
| 158 |
except Exception as e:
|
| 159 |
-
logger.error(f"Error updating guest {guest_id}: {str(e)}")
|
| 160 |
return False
|
| 161 |
|
| 162 |
@staticmethod
|
| 163 |
-
async def delete_guest(guest_id: str) -> bool:
|
| 164 |
-
"""
|
| 165 |
-
Delete a guest profile.
|
| 166 |
-
|
| 167 |
-
Args:
|
| 168 |
-
guest_id: ID of the guest to delete
|
| 169 |
-
|
| 170 |
-
Returns:
|
| 171 |
-
True if successful, False otherwise
|
| 172 |
-
"""
|
| 173 |
try:
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
return True
|
| 181 |
else:
|
| 182 |
-
logger.
|
| 183 |
-
return
|
| 184 |
-
|
| 185 |
except Exception as e:
|
| 186 |
-
logger.error(f"Error deleting guest {guest_id}: {str(e)}")
|
| 187 |
return False
|
| 188 |
|
| 189 |
@staticmethod
|
| 190 |
async def get_guest_count_for_user(customer_id: str) -> int:
|
| 191 |
-
"""
|
| 192 |
-
Get the total number of guests for a user.
|
| 193 |
-
|
| 194 |
-
Args:
|
| 195 |
-
customer_id: ID of the user
|
| 196 |
-
|
| 197 |
-
Returns:
|
| 198 |
-
Number of guests
|
| 199 |
-
"""
|
| 200 |
try:
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
except Exception as e:
|
| 207 |
-
logger.error(f"Error counting guests for user {customer_id}: {str(e)}")
|
| 208 |
return 0
|
| 209 |
|
| 210 |
@staticmethod
|
|
@@ -220,15 +221,62 @@ class GuestModel:
|
|
| 220 |
True if guest belongs to user, False otherwise
|
| 221 |
"""
|
| 222 |
try:
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
except Exception as e:
|
| 233 |
-
logger.error(f"Error
|
| 234 |
return False
|
|
|
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class GuestModel:
|
| 12 |
+
"""Model for managing guest profiles embedded under customer documents"""
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
+
async def create_guest(customer_id: str, guest_data: dict) -> Optional[str]:
|
| 16 |
+
"""Create a new embedded guest profile under a user in customers collection"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
try:
|
| 18 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 19 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 20 |
+
if not user:
|
| 21 |
+
logger.error(f"User not found for customer_id {customer_id}")
|
| 22 |
+
return None
|
| 23 |
+
|
| 24 |
guest_id = str(uuid.uuid4())
|
| 25 |
current_time = datetime.utcnow()
|
| 26 |
+
|
| 27 |
+
guest_doc = {
|
|
|
|
| 28 |
"guest_id": guest_id,
|
| 29 |
+
"first_name": guest_data.get("first_name"),
|
| 30 |
+
"last_name": guest_data.get("last_name"),
|
| 31 |
+
"email": guest_data.get("email"),
|
| 32 |
+
"phone_number": guest_data.get("phone_number"),
|
| 33 |
+
"gender": getattr(guest_data.get("gender"), "value", guest_data.get("gender")),
|
| 34 |
+
"date_of_birth": guest_data.get("date_of_birth"),
|
| 35 |
+
"relationship": getattr(guest_data.get("relationship"), "value", guest_data.get("relationship")),
|
| 36 |
+
"notes": guest_data.get("notes"),
|
| 37 |
+
"is_default": guest_data.get("is_default", False),
|
| 38 |
"created_at": current_time,
|
| 39 |
+
"updated_at": current_time,
|
| 40 |
}
|
| 41 |
|
| 42 |
+
guests = user.get("guests", [])
|
| 43 |
+
# Handle default semantics: if setting this guest as default, unset others.
|
| 44 |
+
if guest_doc.get("is_default"):
|
| 45 |
+
for existing in guests:
|
| 46 |
+
if existing.get("is_default"):
|
| 47 |
+
existing["is_default"] = False
|
| 48 |
+
existing["updated_at"] = current_time
|
| 49 |
+
else:
|
| 50 |
+
# If this is the first guest, make it default by default
|
| 51 |
+
if len(guests) == 0:
|
| 52 |
+
guest_doc["is_default"] = True
|
| 53 |
+
guests.append(guest_doc)
|
| 54 |
+
|
| 55 |
+
update_res = await BookMyServiceUserModel.collection.update_one(
|
| 56 |
+
{"customer_id": customer_id},
|
| 57 |
+
{"$set": {"guests": guests}}
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
if update_res.modified_count > 0:
|
| 61 |
logger.info(f"Guest created successfully: {guest_id} for user: {customer_id}")
|
| 62 |
return guest_id
|
| 63 |
else:
|
| 64 |
+
logger.info(f"Guest creation attempted with no modified_count for user: {customer_id}")
|
| 65 |
+
return guest_id
|
| 66 |
+
|
| 67 |
except Exception as e:
|
| 68 |
+
logger.error(f"Error creating embedded guest for user {customer_id}: {str(e)}")
|
| 69 |
return None
|
| 70 |
|
| 71 |
@staticmethod
|
| 72 |
async def get_user_guests(customer_id: str) -> List[Dict[str, Any]]:
|
| 73 |
+
"""Get all embedded guests for a specific user from customers collection"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
try:
|
| 75 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 76 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 77 |
+
if not user:
|
| 78 |
+
return []
|
| 79 |
+
|
| 80 |
+
guests = user.get("guests", [])
|
| 81 |
+
guests.sort(key=lambda x: x.get("created_at", datetime.utcnow()), reverse=True)
|
| 82 |
+
for g in guests:
|
| 83 |
+
g["customer_id"] = customer_id
|
| 84 |
+
logger.info(f"Retrieved {len(guests)} embedded guests for user: {customer_id}")
|
| 85 |
return guests
|
| 86 |
+
|
| 87 |
except Exception as e:
|
| 88 |
+
logger.error(f"Error getting embedded guests for user {customer_id}: {str(e)}")
|
| 89 |
return []
|
| 90 |
|
| 91 |
@staticmethod
|
| 92 |
+
async def get_guest_by_id(customer_id: str, guest_id: str) -> Optional[Dict[str, Any]]:
|
| 93 |
+
"""Get a specific embedded guest by ID for a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
try:
|
| 95 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 96 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 97 |
+
if not user:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
return None
|
| 99 |
+
|
| 100 |
+
guests = user.get("guests", [])
|
| 101 |
+
for g in guests:
|
| 102 |
+
if g.get("guest_id") == guest_id:
|
| 103 |
+
g_copy = dict(g)
|
| 104 |
+
g_copy["customer_id"] = customer_id
|
| 105 |
+
logger.info(f"Embedded guest found: {guest_id} for user: {customer_id}")
|
| 106 |
+
return g_copy
|
| 107 |
+
logger.warning(f"Embedded guest not found: {guest_id} for user: {customer_id}")
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
except Exception as e:
|
| 111 |
+
logger.error(f"Error getting embedded guest {guest_id} for user {customer_id}: {str(e)}")
|
| 112 |
return None
|
| 113 |
|
| 114 |
@staticmethod
|
| 115 |
+
async def update_guest(customer_id: str, guest_id: str, update_fields: Dict[str, Any]) -> bool:
|
| 116 |
+
"""Update an embedded guest's information under a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
try:
|
| 118 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 119 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 120 |
+
if not user:
|
| 121 |
+
return False
|
| 122 |
|
| 123 |
+
guests = user.get("guests", [])
|
| 124 |
+
updated = False
|
| 125 |
+
|
| 126 |
+
for idx, g in enumerate(guests):
|
| 127 |
+
if g.get("guest_id") == guest_id:
|
| 128 |
+
normalized_updates: Dict[str, Any] = {}
|
| 129 |
+
for k, v in update_fields.items():
|
| 130 |
+
if hasattr(v, "value"):
|
| 131 |
+
normalized_updates[k] = v.value
|
| 132 |
+
else:
|
| 133 |
+
normalized_updates[k] = v
|
| 134 |
+
|
| 135 |
+
normalized_updates["updated_at"] = datetime.utcnow()
|
| 136 |
+
guests[idx] = {**g, **normalized_updates}
|
| 137 |
+
updated = True
|
| 138 |
+
# If is_default is being set to True, unset default for others
|
| 139 |
+
if update_fields.get("is_default"):
|
| 140 |
+
for j, other in enumerate(guests):
|
| 141 |
+
if other.get("guest_id") != guest_id and other.get("is_default"):
|
| 142 |
+
other["is_default"] = False
|
| 143 |
+
other["updated_at"] = datetime.utcnow()
|
| 144 |
+
guests[j] = other
|
| 145 |
+
break
|
| 146 |
+
|
| 147 |
+
if not updated:
|
| 148 |
+
logger.warning(f"Embedded guest not found for update: {guest_id} (user {customer_id})")
|
| 149 |
+
return False
|
| 150 |
+
|
| 151 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 152 |
+
{"customer_id": customer_id},
|
| 153 |
+
{"$set": {"guests": guests}}
|
| 154 |
)
|
| 155 |
+
|
| 156 |
if result.modified_count > 0:
|
| 157 |
+
logger.info(f"Embedded guest updated successfully: {guest_id} (user {customer_id})")
|
| 158 |
return True
|
| 159 |
else:
|
| 160 |
+
logger.info(f"Embedded guest update applied with no modified_count: {guest_id} (user {customer_id})")
|
| 161 |
+
return True
|
| 162 |
+
|
| 163 |
except Exception as e:
|
| 164 |
+
logger.error(f"Error updating embedded guest {guest_id} for user {customer_id}: {str(e)}")
|
| 165 |
return False
|
| 166 |
|
| 167 |
@staticmethod
|
| 168 |
+
async def delete_guest(customer_id: str, guest_id: str) -> bool:
|
| 169 |
+
"""Delete an embedded guest profile under a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
try:
|
| 171 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 172 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 173 |
+
if not user:
|
| 174 |
+
return False
|
| 175 |
+
|
| 176 |
+
guests = user.get("guests", [])
|
| 177 |
+
new_guests = [g for g in guests if g.get("guest_id") != guest_id]
|
| 178 |
+
if len(new_guests) == len(guests):
|
| 179 |
+
logger.warning(f"Embedded guest not found for deletion: {guest_id} (user {customer_id})")
|
| 180 |
+
return False
|
| 181 |
+
|
| 182 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 183 |
+
{"customer_id": customer_id},
|
| 184 |
+
{"$set": {"guests": new_guests}}
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
if result.modified_count > 0:
|
| 188 |
+
logger.info(f"Embedded guest deleted successfully: {guest_id} (user {customer_id})")
|
| 189 |
return True
|
| 190 |
else:
|
| 191 |
+
logger.info(f"Embedded guest deletion applied with no modified_count: {guest_id} (user {customer_id})")
|
| 192 |
+
return True
|
| 193 |
+
|
| 194 |
except Exception as e:
|
| 195 |
+
logger.error(f"Error deleting embedded guest {guest_id} for user {customer_id}: {str(e)}")
|
| 196 |
return False
|
| 197 |
|
| 198 |
@staticmethod
|
| 199 |
async def get_guest_count_for_user(customer_id: str) -> int:
|
| 200 |
+
"""Get the total number of embedded guests for a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
try:
|
| 202 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 203 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 204 |
+
if not user:
|
| 205 |
+
return 0
|
| 206 |
+
return len(user.get("guests", []))
|
| 207 |
except Exception as e:
|
| 208 |
+
logger.error(f"Error counting embedded guests for user {customer_id}: {str(e)}")
|
| 209 |
return 0
|
| 210 |
|
| 211 |
@staticmethod
|
|
|
|
| 221 |
True if guest belongs to user, False otherwise
|
| 222 |
"""
|
| 223 |
try:
|
| 224 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 225 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 226 |
+
if not user:
|
| 227 |
+
return False
|
| 228 |
+
guests = user.get("guests", [])
|
| 229 |
+
return any(g.get("guest_id") == guest_id for g in guests)
|
| 230 |
+
except Exception as e:
|
| 231 |
+
logger.error(f"Error checking embedded guest ownership {guest_id} for user {customer_id}: {str(e)}")
|
| 232 |
+
return False
|
| 233 |
+
|
| 234 |
+
@staticmethod
|
| 235 |
+
async def get_default_guest(customer_id: str) -> Optional[Dict[str, Any]]:
|
| 236 |
+
"""Get the default guest for a user"""
|
| 237 |
+
try:
|
| 238 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 239 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 240 |
+
if not user:
|
| 241 |
+
return None
|
| 242 |
+
guests = user.get("guests", [])
|
| 243 |
+
for g in guests:
|
| 244 |
+
if g.get("is_default"):
|
| 245 |
+
g_copy = dict(g)
|
| 246 |
+
g_copy["customer_id"] = customer_id
|
| 247 |
+
return g_copy
|
| 248 |
+
return None
|
| 249 |
+
except Exception as e:
|
| 250 |
+
logger.error(f"Error getting default guest for user {customer_id}: {str(e)}")
|
| 251 |
+
return None
|
| 252 |
+
|
| 253 |
+
@staticmethod
|
| 254 |
+
async def set_default_guest(customer_id: str, guest_id: str) -> bool:
|
| 255 |
+
"""Set a guest as default for a user, unsetting any existing default"""
|
| 256 |
+
try:
|
| 257 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 258 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 259 |
+
if not user:
|
| 260 |
+
return False
|
| 261 |
+
guests = user.get("guests", [])
|
| 262 |
+
found = False
|
| 263 |
+
now = datetime.utcnow()
|
| 264 |
+
for g in guests:
|
| 265 |
+
if g.get("guest_id") == guest_id:
|
| 266 |
+
g["is_default"] = True
|
| 267 |
+
g["updated_at"] = now
|
| 268 |
+
found = True
|
| 269 |
+
else:
|
| 270 |
+
if g.get("is_default"):
|
| 271 |
+
g["is_default"] = False
|
| 272 |
+
g["updated_at"] = now
|
| 273 |
+
if not found:
|
| 274 |
+
return False
|
| 275 |
+
res = await BookMyServiceUserModel.collection.update_one(
|
| 276 |
+
{"customer_id": customer_id},
|
| 277 |
+
{"$set": {"guests": guests}}
|
| 278 |
+
)
|
| 279 |
+
return True
|
| 280 |
except Exception as e:
|
| 281 |
+
logger.error(f"Error setting default guest {guest_id} for user {customer_id}: {str(e)}")
|
| 282 |
return False
|
app/models/pet_model.py
CHANGED
|
@@ -9,209 +9,254 @@ from app.utils.db import prepare_for_db
|
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class PetModel:
|
| 12 |
-
"""Model for managing pet profiles
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
async def create_pet(
|
| 16 |
customer_id: str,
|
| 17 |
-
pet_data:dict
|
| 18 |
) -> Optional[str]:
|
| 19 |
-
"""
|
| 20 |
-
Create a new pet profile for a user.
|
| 21 |
-
|
| 22 |
-
Args:
|
| 23 |
-
customer_id: ID of the pet owner
|
| 24 |
-
pet_name: Name of the pet
|
| 25 |
-
species: Species (Dog, Cat, Other)
|
| 26 |
-
breed: Breed of the pet
|
| 27 |
-
date_of_birth: Pet's date of birth
|
| 28 |
-
age: Pet's age (if DOB not provided)
|
| 29 |
-
weight: Pet's weight
|
| 30 |
-
gender: Pet's gender
|
| 31 |
-
temperament: Pet's temperament
|
| 32 |
-
health_notes: Health notes including allergies/medications
|
| 33 |
-
is_vaccinated: Vaccination status
|
| 34 |
-
pet_photo_url: URL to pet's photo
|
| 35 |
-
|
| 36 |
-
Returns:
|
| 37 |
-
Pet ID if successful, None otherwise
|
| 38 |
-
"""
|
| 39 |
try:
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
pet_id = str(uuid.uuid4())
|
| 43 |
current_time = datetime.utcnow()
|
| 44 |
|
| 45 |
-
|
| 46 |
-
pet_data = {
|
| 47 |
"pet_id": pet_id,
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
"created_at": current_time,
|
| 61 |
"updated_at": current_time
|
| 62 |
}
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
return pet_id
|
| 71 |
else:
|
| 72 |
-
logger.
|
| 73 |
-
return
|
| 74 |
-
|
| 75 |
except Exception as e:
|
| 76 |
-
logger.error(f"Error creating pet for user {customer_id}: {str(e)}")
|
| 77 |
return None
|
| 78 |
|
| 79 |
@staticmethod
|
| 80 |
async def get_user_pets(customer_id: str) -> List[Dict[str, Any]]:
|
| 81 |
-
"""
|
| 82 |
-
Get all pets for a specific user.
|
| 83 |
-
|
| 84 |
-
Args:
|
| 85 |
-
customer_id: ID of the pet owner
|
| 86 |
-
|
| 87 |
-
Returns:
|
| 88 |
-
List of pet documents
|
| 89 |
-
"""
|
| 90 |
try:
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
logger.info(f"Retrieved {len(pets)} pets for user: {customer_id}")
|
| 101 |
return pets
|
| 102 |
-
|
| 103 |
except Exception as e:
|
| 104 |
-
logger.error(f"Error getting pets for user {customer_id}: {str(e)}")
|
| 105 |
return []
|
| 106 |
|
| 107 |
@staticmethod
|
| 108 |
-
async def get_pet_by_id(pet_id: str) -> Optional[Dict[str, Any]]:
|
| 109 |
-
"""
|
| 110 |
-
Get a specific pet by ID.
|
| 111 |
-
|
| 112 |
-
Args:
|
| 113 |
-
pet_id: ID of the pet
|
| 114 |
-
|
| 115 |
-
Returns:
|
| 116 |
-
Pet document if found, None otherwise
|
| 117 |
-
"""
|
| 118 |
try:
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
if pet:
|
| 124 |
-
pet.pop("_id", None)
|
| 125 |
-
logger.info(f"Pet found: {pet_id}")
|
| 126 |
-
return pet
|
| 127 |
-
else:
|
| 128 |
-
logger.warning(f"Pet not found: {pet_id}")
|
| 129 |
return None
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
except Exception as e:
|
| 132 |
-
logger.error(f"Error getting pet {pet_id}: {str(e)}")
|
| 133 |
return None
|
| 134 |
|
| 135 |
@staticmethod
|
| 136 |
-
async def update_pet(pet_id: str, update_fields: Dict[str, Any]) -> bool:
|
| 137 |
-
"""
|
| 138 |
-
Update a pet's information.
|
| 139 |
-
|
| 140 |
-
Args:
|
| 141 |
-
pet_id: ID of the pet to update
|
| 142 |
-
update_fields: Dictionary of fields to update
|
| 143 |
-
|
| 144 |
-
Returns:
|
| 145 |
-
True if successful, False otherwise
|
| 146 |
-
"""
|
| 147 |
try:
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
)
|
| 159 |
-
|
| 160 |
if result.modified_count > 0:
|
| 161 |
-
logger.info(f"
|
| 162 |
return True
|
| 163 |
else:
|
| 164 |
-
logger.
|
| 165 |
-
return
|
| 166 |
-
|
| 167 |
except Exception as e:
|
| 168 |
-
logger.error(f"Error updating pet {pet_id}: {str(e)}")
|
| 169 |
return False
|
| 170 |
|
| 171 |
@staticmethod
|
| 172 |
-
async def delete_pet(pet_id: str) -> bool:
|
| 173 |
-
"""
|
| 174 |
-
Delete a pet profile.
|
| 175 |
-
|
| 176 |
-
Args:
|
| 177 |
-
pet_id: ID of the pet to delete
|
| 178 |
-
|
| 179 |
-
Returns:
|
| 180 |
-
True if successful, False otherwise
|
| 181 |
-
"""
|
| 182 |
try:
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
return True
|
| 190 |
else:
|
| 191 |
-
logger.
|
| 192 |
-
return
|
| 193 |
-
|
| 194 |
except Exception as e:
|
| 195 |
-
logger.error(f"Error deleting pet {pet_id}: {str(e)}")
|
| 196 |
return False
|
| 197 |
|
| 198 |
@staticmethod
|
| 199 |
async def get_pet_count_for_user(customer_id: str) -> int:
|
| 200 |
-
"""
|
| 201 |
-
Get the total number of pets for a user.
|
| 202 |
-
|
| 203 |
-
Args:
|
| 204 |
-
customer_id: ID of the pet owner
|
| 205 |
-
|
| 206 |
-
Returns:
|
| 207 |
-
Number of pets
|
| 208 |
-
"""
|
| 209 |
try:
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
except Exception as e:
|
| 216 |
-
logger.error(f"Error
|
| 217 |
-
return
|
|
|
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class PetModel:
|
| 12 |
+
"""Model for managing pet profiles embedded under customer documents"""
|
| 13 |
|
| 14 |
@staticmethod
|
| 15 |
async def create_pet(
|
| 16 |
customer_id: str,
|
| 17 |
+
pet_data: dict
|
| 18 |
) -> Optional[str]:
|
| 19 |
+
"""Create a new embedded pet profile under a user in customers collection"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
try:
|
| 21 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 22 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 23 |
+
if not user:
|
| 24 |
+
logger.error(f"User not found for customer_id {customer_id}")
|
| 25 |
+
return None
|
| 26 |
+
|
| 27 |
pet_id = str(uuid.uuid4())
|
| 28 |
current_time = datetime.utcnow()
|
| 29 |
|
| 30 |
+
pet_doc = {
|
|
|
|
| 31 |
"pet_id": pet_id,
|
| 32 |
+
"pet_name": pet_data.get('pet_name'),
|
| 33 |
+
"species": getattr(pet_data.get('species'), 'value', pet_data.get('species')),
|
| 34 |
+
"breed": pet_data.get('breed'),
|
| 35 |
+
"date_of_birth": pet_data.get('date_of_birth'),
|
| 36 |
+
"age": pet_data.get('age'),
|
| 37 |
+
"weight": pet_data.get('weight'),
|
| 38 |
+
"gender": getattr(pet_data.get('gender'), 'value', pet_data.get('gender')),
|
| 39 |
+
"temperament": getattr(pet_data.get('temperament'), 'value', pet_data.get('temperament')),
|
| 40 |
+
"health_notes": pet_data.get('health_notes'),
|
| 41 |
+
"is_vaccinated": pet_data.get('is_vaccinated'),
|
| 42 |
+
"pet_photo_url": pet_data.get('pet_photo_url'),
|
| 43 |
+
"is_default": pet_data.get('is_default', False),
|
| 44 |
"created_at": current_time,
|
| 45 |
"updated_at": current_time
|
| 46 |
}
|
| 47 |
|
| 48 |
+
pets = user.get("pets", [])
|
| 49 |
+
if pet_doc.get("is_default"):
|
| 50 |
+
for existing in pets:
|
| 51 |
+
if existing.get("is_default"):
|
| 52 |
+
existing["is_default"] = False
|
| 53 |
+
existing["updated_at"] = current_time
|
| 54 |
+
else:
|
| 55 |
+
if len(pets) == 0:
|
| 56 |
+
pet_doc["is_default"] = True
|
| 57 |
+
pets.append(pet_doc)
|
| 58 |
+
|
| 59 |
+
update_res = await BookMyServiceUserModel.collection.update_one(
|
| 60 |
+
{"customer_id": customer_id},
|
| 61 |
+
{"$set": {"pets": pets}}
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if update_res.modified_count > 0:
|
| 65 |
+
logger.info(f"Embedded pet created successfully: {pet_id} for user: {customer_id}")
|
| 66 |
return pet_id
|
| 67 |
else:
|
| 68 |
+
logger.info(f"Embedded pet creation attempted with no modified_count for user: {customer_id}")
|
| 69 |
+
return pet_id
|
| 70 |
+
|
| 71 |
except Exception as e:
|
| 72 |
+
logger.error(f"Error creating embedded pet for user {customer_id}: {str(e)}")
|
| 73 |
return None
|
| 74 |
|
| 75 |
@staticmethod
|
| 76 |
async def get_user_pets(customer_id: str) -> List[Dict[str, Any]]:
|
| 77 |
+
"""Get all embedded pets for a specific user from customers collection"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
try:
|
| 79 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 80 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 81 |
+
if not user:
|
| 82 |
+
return []
|
| 83 |
+
|
| 84 |
+
pets = user.get("pets", [])
|
| 85 |
+
pets.sort(key=lambda x: x.get("created_at", datetime.utcnow()), reverse=True)
|
| 86 |
+
for p in pets:
|
| 87 |
+
p["customer_id"] = customer_id
|
| 88 |
+
logger.info(f"Retrieved {len(pets)} embedded pets for user: {customer_id}")
|
| 89 |
return pets
|
| 90 |
+
|
| 91 |
except Exception as e:
|
| 92 |
+
logger.error(f"Error getting embedded pets for user {customer_id}: {str(e)}")
|
| 93 |
return []
|
| 94 |
|
| 95 |
@staticmethod
|
| 96 |
+
async def get_pet_by_id(customer_id: str, pet_id: str) -> Optional[Dict[str, Any]]:
|
| 97 |
+
"""Get a specific embedded pet by ID for a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
try:
|
| 99 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 100 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 101 |
+
if not user:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
return None
|
| 103 |
+
|
| 104 |
+
pets = user.get("pets", [])
|
| 105 |
+
for p in pets:
|
| 106 |
+
if p.get("pet_id") == pet_id:
|
| 107 |
+
p_copy = dict(p)
|
| 108 |
+
p_copy["customer_id"] = customer_id
|
| 109 |
+
logger.info(f"Embedded pet found: {pet_id} for user: {customer_id}")
|
| 110 |
+
return p_copy
|
| 111 |
+
logger.warning(f"Embedded pet not found: {pet_id} for user: {customer_id}")
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
except Exception as e:
|
| 115 |
+
logger.error(f"Error getting embedded pet {pet_id} for user {customer_id}: {str(e)}")
|
| 116 |
return None
|
| 117 |
|
| 118 |
@staticmethod
|
| 119 |
+
async def update_pet(customer_id: str, pet_id: str, update_fields: Dict[str, Any]) -> bool:
|
| 120 |
+
"""Update an embedded pet's information under a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
try:
|
| 122 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 123 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 124 |
+
if not user:
|
| 125 |
+
return False
|
| 126 |
+
|
| 127 |
+
pets = user.get("pets", [])
|
| 128 |
+
updated = False
|
| 129 |
+
|
| 130 |
+
for idx, p in enumerate(pets):
|
| 131 |
+
if p.get("pet_id") == pet_id:
|
| 132 |
+
normalized_updates: Dict[str, Any] = {}
|
| 133 |
+
for k, v in update_fields.items():
|
| 134 |
+
if hasattr(v, "value"):
|
| 135 |
+
normalized_updates[k] = v.value
|
| 136 |
+
else:
|
| 137 |
+
normalized_updates[k] = v
|
| 138 |
+
|
| 139 |
+
normalized_updates["updated_at"] = datetime.utcnow()
|
| 140 |
+
pets[idx] = {**p, **normalized_updates}
|
| 141 |
+
updated = True
|
| 142 |
+
if update_fields.get("is_default"):
|
| 143 |
+
for j, other in enumerate(pets):
|
| 144 |
+
if other.get("pet_id") != pet_id and other.get("is_default"):
|
| 145 |
+
other["is_default"] = False
|
| 146 |
+
other["updated_at"] = datetime.utcnow()
|
| 147 |
+
pets[j] = other
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
if not updated:
|
| 151 |
+
logger.warning(f"Embedded pet not found for update: {pet_id} (user {customer_id})")
|
| 152 |
+
return False
|
| 153 |
+
|
| 154 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 155 |
+
{"customer_id": customer_id},
|
| 156 |
+
{"$set": {"pets": pets}}
|
| 157 |
)
|
| 158 |
+
|
| 159 |
if result.modified_count > 0:
|
| 160 |
+
logger.info(f"Embedded pet updated successfully: {pet_id} (user {customer_id})")
|
| 161 |
return True
|
| 162 |
else:
|
| 163 |
+
logger.info(f"Embedded pet update applied with no modified_count: {pet_id} (user {customer_id})")
|
| 164 |
+
return True
|
| 165 |
+
|
| 166 |
except Exception as e:
|
| 167 |
+
logger.error(f"Error updating embedded pet {pet_id} for user {customer_id}: {str(e)}")
|
| 168 |
return False
|
| 169 |
|
| 170 |
@staticmethod
|
| 171 |
+
async def delete_pet(customer_id: str, pet_id: str) -> bool:
|
| 172 |
+
"""Delete an embedded pet profile under a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
try:
|
| 174 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 175 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 176 |
+
if not user:
|
| 177 |
+
return False
|
| 178 |
+
|
| 179 |
+
pets = user.get("pets", [])
|
| 180 |
+
new_pets = [p for p in pets if p.get("pet_id") != pet_id]
|
| 181 |
+
if len(new_pets) == len(pets):
|
| 182 |
+
logger.warning(f"Embedded pet not found for deletion: {pet_id} (user {customer_id})")
|
| 183 |
+
return False
|
| 184 |
+
|
| 185 |
+
result = await BookMyServiceUserModel.collection.update_one(
|
| 186 |
+
{"customer_id": customer_id},
|
| 187 |
+
{"$set": {"pets": new_pets}}
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
if result.modified_count > 0:
|
| 191 |
+
logger.info(f"Embedded pet deleted successfully: {pet_id} (user {customer_id})")
|
| 192 |
return True
|
| 193 |
else:
|
| 194 |
+
logger.info(f"Embedded pet deletion applied with no modified_count: {pet_id} (user {customer_id})")
|
| 195 |
+
return True
|
| 196 |
+
|
| 197 |
except Exception as e:
|
| 198 |
+
logger.error(f"Error deleting embedded pet {pet_id} for user {customer_id}: {str(e)}")
|
| 199 |
return False
|
| 200 |
|
| 201 |
@staticmethod
|
| 202 |
async def get_pet_count_for_user(customer_id: str) -> int:
|
| 203 |
+
"""Get the total number of embedded pets for a user"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
try:
|
| 205 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 206 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 207 |
+
if not user:
|
| 208 |
+
return 0
|
| 209 |
+
return len(user.get("pets", []))
|
| 210 |
+
except Exception as e:
|
| 211 |
+
logger.error(f"Error counting embedded pets for user {customer_id}: {str(e)}")
|
| 212 |
+
return 0
|
| 213 |
+
|
| 214 |
+
@staticmethod
|
| 215 |
+
async def get_default_pet(customer_id: str) -> Optional[Dict[str, Any]]:
|
| 216 |
+
"""Get the default pet for a user"""
|
| 217 |
+
try:
|
| 218 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 219 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 220 |
+
if not user:
|
| 221 |
+
return None
|
| 222 |
+
pets = user.get("pets", [])
|
| 223 |
+
for p in pets:
|
| 224 |
+
if p.get("is_default"):
|
| 225 |
+
p_copy = dict(p)
|
| 226 |
+
p_copy["customer_id"] = customer_id
|
| 227 |
+
return p_copy
|
| 228 |
+
return None
|
| 229 |
+
except Exception as e:
|
| 230 |
+
logger.error(f"Error getting default pet for user {customer_id}: {str(e)}")
|
| 231 |
+
return None
|
| 232 |
+
|
| 233 |
+
@staticmethod
|
| 234 |
+
async def set_default_pet(customer_id: str, pet_id: str) -> bool:
|
| 235 |
+
"""Set a pet as default for a user, unsetting any existing default"""
|
| 236 |
+
try:
|
| 237 |
+
from app.models.user_model import BookMyServiceUserModel
|
| 238 |
+
user = await BookMyServiceUserModel.collection.find_one({"customer_id": customer_id})
|
| 239 |
+
if not user:
|
| 240 |
+
return False
|
| 241 |
+
pets = user.get("pets", [])
|
| 242 |
+
found = False
|
| 243 |
+
now = datetime.utcnow()
|
| 244 |
+
for p in pets:
|
| 245 |
+
if p.get("pet_id") == pet_id:
|
| 246 |
+
p["is_default"] = True
|
| 247 |
+
p["updated_at"] = now
|
| 248 |
+
found = True
|
| 249 |
+
else:
|
| 250 |
+
if p.get("is_default"):
|
| 251 |
+
p["is_default"] = False
|
| 252 |
+
p["updated_at"] = now
|
| 253 |
+
if not found:
|
| 254 |
+
return False
|
| 255 |
+
res = await BookMyServiceUserModel.collection.update_one(
|
| 256 |
+
{"customer_id": customer_id},
|
| 257 |
+
{"$set": {"pets": pets}}
|
| 258 |
+
)
|
| 259 |
+
return True
|
| 260 |
except Exception as e:
|
| 261 |
+
logger.error(f"Error setting default pet {pet_id} for user {customer_id}: {str(e)}")
|
| 262 |
+
return False
|
app/routers/address_router.py
CHANGED
|
@@ -28,18 +28,15 @@ async def get_user_addresses(current_customer_id: str = Depends(get_current_cust
|
|
| 28 |
address_responses = []
|
| 29 |
for addr in addresses:
|
| 30 |
address_responses.append(AddressResponse(
|
| 31 |
-
address_id=addr["
|
| 32 |
-
customer_id=addr["customer_id"],
|
| 33 |
address_type=addr["address_type"],
|
| 34 |
-
contact_name=addr["contact_name"],
|
| 35 |
-
contact_phone=addr["contact_phone"],
|
| 36 |
address_line_1=addr["address_line_1"],
|
| 37 |
-
address_line_2=addr.get("address_line_2"),
|
| 38 |
city=addr["city"],
|
| 39 |
state=addr["state"],
|
| 40 |
postal_code=addr["postal_code"],
|
| 41 |
-
country=addr
|
| 42 |
-
landmark=addr
|
| 43 |
is_default=addr.get("is_default", False),
|
| 44 |
created_at=addr.get("created_at"),
|
| 45 |
updated_at=addr.get("updated_at")
|
|
@@ -89,7 +86,7 @@ async def create_address(
|
|
| 89 |
# If this is the first address, make it default
|
| 90 |
is_default = len(existing_addresses) == 0 or address_data.is_default
|
| 91 |
|
| 92 |
-
address_id = await AddressModel.create_address(current_customer_id,address_data.dict())
|
| 93 |
|
| 94 |
|
| 95 |
if address_id:
|
|
@@ -97,19 +94,16 @@ async def create_address(
|
|
| 97 |
created_address = await AddressModel.get_address_by_id(current_customer_id,address_id)
|
| 98 |
|
| 99 |
address_response = AddressResponse(
|
| 100 |
-
address_id=address_id,
|
| 101 |
-
customer_id=created_address["customer_id"],
|
| 102 |
address_type=created_address["address_type"],
|
| 103 |
-
contact_name=created_address["contact_name"],
|
| 104 |
-
contact_phone=created_address["contact_phone"],
|
| 105 |
address_line_1=created_address["address_line_1"],
|
| 106 |
-
address_line_2=created_address.get("address_line_2"),
|
| 107 |
city=created_address["city"],
|
| 108 |
state=created_address["state"],
|
| 109 |
postal_code=created_address["postal_code"],
|
| 110 |
-
country=created_address
|
| 111 |
is_default=created_address.get("is_default", False),
|
| 112 |
-
landmark=created_address
|
| 113 |
created_at=created_address.get("created_at"),
|
| 114 |
updated_at=created_address.get("updated_at")
|
| 115 |
)
|
|
@@ -158,10 +152,6 @@ async def update_address(
|
|
| 158 |
|
| 159 |
if address_data.address_type is not None:
|
| 160 |
update_fields["address_type"] = address_data.address_type
|
| 161 |
-
if address_data.contact_name is not None:
|
| 162 |
-
update_fields["contact_name"] = address_data.contact_name
|
| 163 |
-
if address_data.contact_phone is not None:
|
| 164 |
-
update_fields["contact_phone"] = address_data.contact_phone
|
| 165 |
if address_data.address_line_1 is not None:
|
| 166 |
update_fields["address_line_1"] = address_data.address_line_1
|
| 167 |
if address_data.address_line_2 is not None:
|
|
@@ -188,18 +178,15 @@ async def update_address(
|
|
| 188 |
updated_address = await AddressModel.get_address_by_id(current_customer_id,address_id)
|
| 189 |
|
| 190 |
address_response = AddressResponse(
|
| 191 |
-
address_id=address_id,
|
| 192 |
-
customer_id=updated_address["customer_id"],
|
| 193 |
address_type=updated_address["address_type"],
|
| 194 |
-
contact_name=updated_address["contact_name"],
|
| 195 |
-
contact_phone=updated_address["contact_phone"],
|
| 196 |
address_line_1=updated_address["address_line_1"],
|
| 197 |
-
address_line_2=updated_address.get("address_line_2"),
|
| 198 |
city=updated_address["city"],
|
| 199 |
state=updated_address["state"],
|
| 200 |
postal_code=updated_address["postal_code"],
|
| 201 |
-
country=updated_address
|
| 202 |
-
landmark=updated_address
|
| 203 |
is_default=updated_address.get("is_default", False),
|
| 204 |
created_at=updated_address.get("created_at"),
|
| 205 |
updated_at=updated_address.get("updated_at")
|
|
@@ -248,11 +235,12 @@ async def delete_address(
|
|
| 248 |
if existing_address.get("is_default", False):
|
| 249 |
# Get other addresses to potentially set a new default
|
| 250 |
user_addresses = await AddressModel.get_user_addresses(current_customer_id)
|
| 251 |
-
|
|
|
|
| 252 |
|
| 253 |
if other_addresses:
|
| 254 |
# Set the first other address as default
|
| 255 |
-
await AddressModel.set_default_address(current_customer_id, other_addresses[0]["
|
| 256 |
|
| 257 |
success = await AddressModel.delete_address(current_customer_id,address_id)
|
| 258 |
|
|
@@ -303,17 +291,16 @@ async def set_default_address(
|
|
| 303 |
|
| 304 |
address_response = AddressResponse(
|
| 305 |
address_id=request.address_id,
|
| 306 |
-
customer_id=updated_address["customer_id"],
|
| 307 |
address_type=updated_address["address_type"],
|
| 308 |
-
contact_name=updated_address
|
| 309 |
-
contact_phone=updated_address
|
| 310 |
address_line_1=updated_address["address_line_1"],
|
| 311 |
-
address_line_2=updated_address.get("address_line_2"),
|
| 312 |
city=updated_address["city"],
|
| 313 |
state=updated_address["state"],
|
| 314 |
postal_code=updated_address["postal_code"],
|
| 315 |
-
country=updated_address
|
| 316 |
-
landmark=updated_address
|
| 317 |
is_default=updated_address.get("is_default", False),
|
| 318 |
created_at=updated_address.get("created_at"),
|
| 319 |
updated_at=updated_address.get("updated_at")
|
|
|
|
| 28 |
address_responses = []
|
| 29 |
for addr in addresses:
|
| 30 |
address_responses.append(AddressResponse(
|
| 31 |
+
address_id=addr["address_id"], # Use the new address_id field
|
|
|
|
| 32 |
address_type=addr["address_type"],
|
|
|
|
|
|
|
| 33 |
address_line_1=addr["address_line_1"],
|
| 34 |
+
address_line_2=addr.get("address_line_2", ""),
|
| 35 |
city=addr["city"],
|
| 36 |
state=addr["state"],
|
| 37 |
postal_code=addr["postal_code"],
|
| 38 |
+
country=addr.get("country", "India"),
|
| 39 |
+
landmark=addr.get("landmark", ""),
|
| 40 |
is_default=addr.get("is_default", False),
|
| 41 |
created_at=addr.get("created_at"),
|
| 42 |
updated_at=addr.get("updated_at")
|
|
|
|
| 86 |
# If this is the first address, make it default
|
| 87 |
is_default = len(existing_addresses) == 0 or address_data.is_default
|
| 88 |
|
| 89 |
+
address_id = await AddressModel.create_address(current_customer_id, address_data.dict())
|
| 90 |
|
| 91 |
|
| 92 |
if address_id:
|
|
|
|
| 94 |
created_address = await AddressModel.get_address_by_id(current_customer_id,address_id)
|
| 95 |
|
| 96 |
address_response = AddressResponse(
|
| 97 |
+
address_id=created_address["address_id"], # Use the new address_id field
|
|
|
|
| 98 |
address_type=created_address["address_type"],
|
|
|
|
|
|
|
| 99 |
address_line_1=created_address["address_line_1"],
|
| 100 |
+
address_line_2=created_address.get("address_line_2", ""),
|
| 101 |
city=created_address["city"],
|
| 102 |
state=created_address["state"],
|
| 103 |
postal_code=created_address["postal_code"],
|
| 104 |
+
country=created_address.get("country", "India"),
|
| 105 |
is_default=created_address.get("is_default", False),
|
| 106 |
+
landmark=created_address.get("landmark", ""),
|
| 107 |
created_at=created_address.get("created_at"),
|
| 108 |
updated_at=created_address.get("updated_at")
|
| 109 |
)
|
|
|
|
| 152 |
|
| 153 |
if address_data.address_type is not None:
|
| 154 |
update_fields["address_type"] = address_data.address_type
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
if address_data.address_line_1 is not None:
|
| 156 |
update_fields["address_line_1"] = address_data.address_line_1
|
| 157 |
if address_data.address_line_2 is not None:
|
|
|
|
| 178 |
updated_address = await AddressModel.get_address_by_id(current_customer_id,address_id)
|
| 179 |
|
| 180 |
address_response = AddressResponse(
|
| 181 |
+
address_id=updated_address["address_id"], # Use the new address_id field
|
|
|
|
| 182 |
address_type=updated_address["address_type"],
|
|
|
|
|
|
|
| 183 |
address_line_1=updated_address["address_line_1"],
|
| 184 |
+
address_line_2=updated_address.get("address_line_2", ""),
|
| 185 |
city=updated_address["city"],
|
| 186 |
state=updated_address["state"],
|
| 187 |
postal_code=updated_address["postal_code"],
|
| 188 |
+
country=updated_address.get("country", "India"),
|
| 189 |
+
landmark=updated_address.get("landmark", ""),
|
| 190 |
is_default=updated_address.get("is_default", False),
|
| 191 |
created_at=updated_address.get("created_at"),
|
| 192 |
updated_at=updated_address.get("updated_at")
|
|
|
|
| 235 |
if existing_address.get("is_default", False):
|
| 236 |
# Get other addresses to potentially set a new default
|
| 237 |
user_addresses = await AddressModel.get_user_addresses(current_customer_id)
|
| 238 |
+
# Compare by new domain id field 'address_id'
|
| 239 |
+
other_addresses = [addr for addr in user_addresses if addr.get("address_id") != address_id]
|
| 240 |
|
| 241 |
if other_addresses:
|
| 242 |
# Set the first other address as default
|
| 243 |
+
await AddressModel.set_default_address(current_customer_id, other_addresses[0]["address_id"])
|
| 244 |
|
| 245 |
success = await AddressModel.delete_address(current_customer_id,address_id)
|
| 246 |
|
|
|
|
| 291 |
|
| 292 |
address_response = AddressResponse(
|
| 293 |
address_id=request.address_id,
|
|
|
|
| 294 |
address_type=updated_address["address_type"],
|
| 295 |
+
contact_name=updated_address.get("contact_name", ""),
|
| 296 |
+
contact_phone=updated_address.get("contact_phone", ""),
|
| 297 |
address_line_1=updated_address["address_line_1"],
|
| 298 |
+
address_line_2=updated_address.get("address_line_2", ""),
|
| 299 |
city=updated_address["city"],
|
| 300 |
state=updated_address["state"],
|
| 301 |
postal_code=updated_address["postal_code"],
|
| 302 |
+
country=updated_address.get("country", "India"),
|
| 303 |
+
landmark=updated_address.get("landmark", ""),
|
| 304 |
is_default=updated_address.get("is_default", False),
|
| 305 |
created_at=updated_address.get("created_at"),
|
| 306 |
updated_at=updated_address.get("updated_at")
|
app/routers/guest_router.py
CHANGED
|
@@ -6,7 +6,8 @@ from app.schemas.guest_schema import (
|
|
| 6 |
GuestUpdateRequest,
|
| 7 |
GuestResponse,
|
| 8 |
GuestListResponse,
|
| 9 |
-
GuestDeleteResponse
|
|
|
|
| 10 |
)
|
| 11 |
from app.utils.jwt import verify_token
|
| 12 |
from typing import Dict, Any
|
|
@@ -95,7 +96,7 @@ async def create_guest(
|
|
| 95 |
)
|
| 96 |
|
| 97 |
# Retrieve and return the created guest
|
| 98 |
-
created_guest = await GuestModel.get_guest_by_id(guest_id)
|
| 99 |
if not created_guest:
|
| 100 |
raise HTTPException(
|
| 101 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -113,6 +114,70 @@ async def create_guest(
|
|
| 113 |
detail="Failed to create guest profile"
|
| 114 |
)
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
@router.put("/guests/{guest_id}", response_model=GuestResponse)
|
| 117 |
async def update_guest(
|
| 118 |
guest_id: str,
|
|
@@ -132,7 +197,7 @@ async def update_guest(
|
|
| 132 |
customer_id=current_user.get("sub")
|
| 133 |
|
| 134 |
# Check if guest exists and belongs to user
|
| 135 |
-
existing_guest = await GuestModel.get_guest_by_id(guest_id)
|
| 136 |
if not existing_guest:
|
| 137 |
raise HTTPException(
|
| 138 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
@@ -161,7 +226,7 @@ async def update_guest(
|
|
| 161 |
)
|
| 162 |
|
| 163 |
# Update guest in database
|
| 164 |
-
success = await GuestModel.update_guest(guest_id, update_fields)
|
| 165 |
if not success:
|
| 166 |
raise HTTPException(
|
| 167 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -169,7 +234,7 @@ async def update_guest(
|
|
| 169 |
)
|
| 170 |
|
| 171 |
# Retrieve and return updated guest
|
| 172 |
-
updated_guest = await GuestModel.get_guest_by_id(guest_id)
|
| 173 |
if not updated_guest:
|
| 174 |
raise HTTPException(
|
| 175 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -204,7 +269,7 @@ async def delete_guest(
|
|
| 204 |
customer_id=current_user.get("sub")
|
| 205 |
|
| 206 |
# Check if guest exists and belongs to user
|
| 207 |
-
existing_guest = await GuestModel.get_guest_by_id(guest_id)
|
| 208 |
if not existing_guest:
|
| 209 |
raise HTTPException(
|
| 210 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
@@ -218,7 +283,7 @@ async def delete_guest(
|
|
| 218 |
)
|
| 219 |
|
| 220 |
# Delete guest from database
|
| 221 |
-
success = await GuestModel.delete_guest(guest_id)
|
| 222 |
if not success:
|
| 223 |
raise HTTPException(
|
| 224 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 6 |
GuestUpdateRequest,
|
| 7 |
GuestResponse,
|
| 8 |
GuestListResponse,
|
| 9 |
+
GuestDeleteResponse,
|
| 10 |
+
SetDefaultGuestRequest
|
| 11 |
)
|
| 12 |
from app.utils.jwt import verify_token
|
| 13 |
from typing import Dict, Any
|
|
|
|
| 96 |
)
|
| 97 |
|
| 98 |
# Retrieve and return the created guest
|
| 99 |
+
created_guest = await GuestModel.get_guest_by_id(customer_id, guest_id)
|
| 100 |
if not created_guest:
|
| 101 |
raise HTTPException(
|
| 102 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 114 |
detail="Failed to create guest profile"
|
| 115 |
)
|
| 116 |
|
| 117 |
+
@router.get("/guests/default", response_model=GuestResponse)
|
| 118 |
+
async def get_default_guest(
|
| 119 |
+
current_user: Dict[str, Any] = Depends(get_current_user)
|
| 120 |
+
):
|
| 121 |
+
"""Get the default guest for the current user"""
|
| 122 |
+
try:
|
| 123 |
+
customer_id = current_user.get("sub")
|
| 124 |
+
default_guest = await GuestModel.get_default_guest(customer_id)
|
| 125 |
+
if not default_guest:
|
| 126 |
+
raise HTTPException(
|
| 127 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 128 |
+
detail="Default guest not set"
|
| 129 |
+
)
|
| 130 |
+
return GuestResponse(**default_guest)
|
| 131 |
+
except HTTPException:
|
| 132 |
+
raise
|
| 133 |
+
except Exception as e:
|
| 134 |
+
logger.error(f"Error getting default guest for user {customer_id}: {str(e)}")
|
| 135 |
+
raise HTTPException(
|
| 136 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 137 |
+
detail="Failed to retrieve default guest"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
@router.post("/guests/set-default", response_model=GuestResponse)
|
| 141 |
+
async def set_default_guest(
|
| 142 |
+
req: SetDefaultGuestRequest,
|
| 143 |
+
current_user: Dict[str, Any] = Depends(get_current_user)
|
| 144 |
+
):
|
| 145 |
+
"""Set a guest as default for the current user"""
|
| 146 |
+
try:
|
| 147 |
+
customer_id = current_user.get("sub")
|
| 148 |
+
# Verify guest exists and belongs to user
|
| 149 |
+
existing_guest = await GuestModel.get_guest_by_id(customer_id, req.guest_id)
|
| 150 |
+
if not existing_guest:
|
| 151 |
+
raise HTTPException(
|
| 152 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 153 |
+
detail="Guest not found"
|
| 154 |
+
)
|
| 155 |
+
if existing_guest.get("customer_id") != customer_id:
|
| 156 |
+
raise HTTPException(
|
| 157 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
| 158 |
+
detail="Access denied. This guest doesn't belong to you."
|
| 159 |
+
)
|
| 160 |
+
success = await GuestModel.set_default_guest(customer_id, req.guest_id)
|
| 161 |
+
if not success:
|
| 162 |
+
raise HTTPException(
|
| 163 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 164 |
+
detail="Failed to set default guest"
|
| 165 |
+
)
|
| 166 |
+
updated_guest = await GuestModel.get_guest_by_id(customer_id, req.guest_id)
|
| 167 |
+
if not updated_guest:
|
| 168 |
+
raise HTTPException(
|
| 169 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 170 |
+
detail="Default set but failed to retrieve guest"
|
| 171 |
+
)
|
| 172 |
+
return GuestResponse(**updated_guest)
|
| 173 |
+
except HTTPException:
|
| 174 |
+
raise
|
| 175 |
+
except Exception as e:
|
| 176 |
+
logger.error(f"Error setting default guest {req.guest_id} for user {customer_id}: {str(e)}")
|
| 177 |
+
raise HTTPException(
|
| 178 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 179 |
+
detail="Failed to set default guest"
|
| 180 |
+
)
|
| 181 |
@router.put("/guests/{guest_id}", response_model=GuestResponse)
|
| 182 |
async def update_guest(
|
| 183 |
guest_id: str,
|
|
|
|
| 197 |
customer_id=current_user.get("sub")
|
| 198 |
|
| 199 |
# Check if guest exists and belongs to user
|
| 200 |
+
existing_guest = await GuestModel.get_guest_by_id(customer_id, guest_id)
|
| 201 |
if not existing_guest:
|
| 202 |
raise HTTPException(
|
| 203 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
| 226 |
)
|
| 227 |
|
| 228 |
# Update guest in database
|
| 229 |
+
success = await GuestModel.update_guest(customer_id, guest_id, update_fields)
|
| 230 |
if not success:
|
| 231 |
raise HTTPException(
|
| 232 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 234 |
)
|
| 235 |
|
| 236 |
# Retrieve and return updated guest
|
| 237 |
+
updated_guest = await GuestModel.get_guest_by_id(customer_id, guest_id)
|
| 238 |
if not updated_guest:
|
| 239 |
raise HTTPException(
|
| 240 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 269 |
customer_id=current_user.get("sub")
|
| 270 |
|
| 271 |
# Check if guest exists and belongs to user
|
| 272 |
+
existing_guest = await GuestModel.get_guest_by_id(customer_id, guest_id)
|
| 273 |
if not existing_guest:
|
| 274 |
raise HTTPException(
|
| 275 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
| 283 |
)
|
| 284 |
|
| 285 |
# Delete guest from database
|
| 286 |
+
success = await GuestModel.delete_guest(customer_id, guest_id)
|
| 287 |
if not success:
|
| 288 |
raise HTTPException(
|
| 289 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
app/routers/pet_router.py
CHANGED
|
@@ -6,7 +6,8 @@ from app.schemas.pet_schema import (
|
|
| 6 |
PetUpdateRequest,
|
| 7 |
PetResponse,
|
| 8 |
PetListResponse,
|
| 9 |
-
PetDeleteResponse
|
|
|
|
| 10 |
)
|
| 11 |
from app.utils.jwt import verify_token
|
| 12 |
from typing import Dict, Any
|
|
@@ -95,7 +96,7 @@ async def create_pet(
|
|
| 95 |
)
|
| 96 |
|
| 97 |
# Retrieve and return the created pet
|
| 98 |
-
created_pet = await PetModel.get_pet_by_id(pet_id)
|
| 99 |
if not created_pet:
|
| 100 |
raise HTTPException(
|
| 101 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -113,6 +114,70 @@ async def create_pet(
|
|
| 113 |
detail="Failed to create pet profile"
|
| 114 |
)
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
@router.put("/pets/{pet_id}", response_model=PetResponse)
|
| 117 |
async def update_pet(
|
| 118 |
pet_id: str,
|
|
@@ -132,7 +197,7 @@ async def update_pet(
|
|
| 132 |
customer_id=current_user.get("sub")
|
| 133 |
|
| 134 |
# Check if pet exists and belongs to user
|
| 135 |
-
existing_pet = await PetModel.get_pet_by_id(pet_id)
|
| 136 |
if not existing_pet:
|
| 137 |
raise HTTPException(
|
| 138 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
@@ -161,7 +226,7 @@ async def update_pet(
|
|
| 161 |
)
|
| 162 |
|
| 163 |
# Update pet in database
|
| 164 |
-
success = await PetModel.update_pet(pet_id, update_fields)
|
| 165 |
if not success:
|
| 166 |
raise HTTPException(
|
| 167 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -169,7 +234,7 @@ async def update_pet(
|
|
| 169 |
)
|
| 170 |
|
| 171 |
# Retrieve and return updated pet
|
| 172 |
-
updated_pet = await PetModel.get_pet_by_id(pet_id)
|
| 173 |
if not updated_pet:
|
| 174 |
raise HTTPException(
|
| 175 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -204,7 +269,7 @@ async def delete_pet(
|
|
| 204 |
customer_id=current_user.get("sub")
|
| 205 |
|
| 206 |
# Check if pet exists and belongs to user
|
| 207 |
-
existing_pet = await PetModel.get_pet_by_id(pet_id)
|
| 208 |
if not existing_pet:
|
| 209 |
raise HTTPException(
|
| 210 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
@@ -218,7 +283,7 @@ async def delete_pet(
|
|
| 218 |
)
|
| 219 |
|
| 220 |
# Delete pet from database
|
| 221 |
-
success = await PetModel.delete_pet(pet_id)
|
| 222 |
if not success:
|
| 223 |
raise HTTPException(
|
| 224 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 6 |
PetUpdateRequest,
|
| 7 |
PetResponse,
|
| 8 |
PetListResponse,
|
| 9 |
+
PetDeleteResponse,
|
| 10 |
+
SetDefaultPetRequest
|
| 11 |
)
|
| 12 |
from app.utils.jwt import verify_token
|
| 13 |
from typing import Dict, Any
|
|
|
|
| 96 |
)
|
| 97 |
|
| 98 |
# Retrieve and return the created pet
|
| 99 |
+
created_pet = await PetModel.get_pet_by_id(customer_id, pet_id)
|
| 100 |
if not created_pet:
|
| 101 |
raise HTTPException(
|
| 102 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 114 |
detail="Failed to create pet profile"
|
| 115 |
)
|
| 116 |
|
| 117 |
+
@router.get("/pets/default", response_model=PetResponse)
|
| 118 |
+
async def get_default_pet(
|
| 119 |
+
current_user: Dict[str, Any] = Depends(get_current_user)
|
| 120 |
+
):
|
| 121 |
+
"""Get the default pet for the current user"""
|
| 122 |
+
try:
|
| 123 |
+
customer_id = current_user.get("sub")
|
| 124 |
+
default_pet = await PetModel.get_default_pet(customer_id)
|
| 125 |
+
if not default_pet:
|
| 126 |
+
raise HTTPException(
|
| 127 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 128 |
+
detail="Default pet not set"
|
| 129 |
+
)
|
| 130 |
+
return PetResponse(**default_pet)
|
| 131 |
+
except HTTPException:
|
| 132 |
+
raise
|
| 133 |
+
except Exception as e:
|
| 134 |
+
logger.error(f"Error getting default pet for user {customer_id}: {str(e)}")
|
| 135 |
+
raise HTTPException(
|
| 136 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 137 |
+
detail="Failed to retrieve default pet"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
@router.post("/pets/set-default", response_model=PetResponse)
|
| 141 |
+
async def set_default_pet(
|
| 142 |
+
req: SetDefaultPetRequest,
|
| 143 |
+
current_user: Dict[str, Any] = Depends(get_current_user)
|
| 144 |
+
):
|
| 145 |
+
"""Set a pet as default for the current user"""
|
| 146 |
+
try:
|
| 147 |
+
customer_id = current_user.get("sub")
|
| 148 |
+
# Verify pet exists and belongs to user
|
| 149 |
+
existing_pet = await PetModel.get_pet_by_id(customer_id, req.pet_id)
|
| 150 |
+
if not existing_pet:
|
| 151 |
+
raise HTTPException(
|
| 152 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 153 |
+
detail="Pet not found"
|
| 154 |
+
)
|
| 155 |
+
if existing_pet.get("customer_id") != customer_id:
|
| 156 |
+
raise HTTPException(
|
| 157 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
| 158 |
+
detail="Access denied. This pet doesn't belong to you."
|
| 159 |
+
)
|
| 160 |
+
success = await PetModel.set_default_pet(customer_id, req.pet_id)
|
| 161 |
+
if not success:
|
| 162 |
+
raise HTTPException(
|
| 163 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 164 |
+
detail="Failed to set default pet"
|
| 165 |
+
)
|
| 166 |
+
updated_pet = await PetModel.get_pet_by_id(customer_id, req.pet_id)
|
| 167 |
+
if not updated_pet:
|
| 168 |
+
raise HTTPException(
|
| 169 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 170 |
+
detail="Default set but failed to retrieve pet"
|
| 171 |
+
)
|
| 172 |
+
return PetResponse(**updated_pet)
|
| 173 |
+
except HTTPException:
|
| 174 |
+
raise
|
| 175 |
+
except Exception as e:
|
| 176 |
+
logger.error(f"Error setting default pet {req.pet_id} for user {customer_id}: {str(e)}")
|
| 177 |
+
raise HTTPException(
|
| 178 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 179 |
+
detail="Failed to set default pet"
|
| 180 |
+
)
|
| 181 |
@router.put("/pets/{pet_id}", response_model=PetResponse)
|
| 182 |
async def update_pet(
|
| 183 |
pet_id: str,
|
|
|
|
| 197 |
customer_id=current_user.get("sub")
|
| 198 |
|
| 199 |
# Check if pet exists and belongs to user
|
| 200 |
+
existing_pet = await PetModel.get_pet_by_id(customer_id, pet_id)
|
| 201 |
if not existing_pet:
|
| 202 |
raise HTTPException(
|
| 203 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
| 226 |
)
|
| 227 |
|
| 228 |
# Update pet in database
|
| 229 |
+
success = await PetModel.update_pet(customer_id, pet_id, update_fields)
|
| 230 |
if not success:
|
| 231 |
raise HTTPException(
|
| 232 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 234 |
)
|
| 235 |
|
| 236 |
# Retrieve and return updated pet
|
| 237 |
+
updated_pet = await PetModel.get_pet_by_id(customer_id, pet_id)
|
| 238 |
if not updated_pet:
|
| 239 |
raise HTTPException(
|
| 240 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
| 269 |
customer_id=current_user.get("sub")
|
| 270 |
|
| 271 |
# Check if pet exists and belongs to user
|
| 272 |
+
existing_pet = await PetModel.get_pet_by_id(customer_id, pet_id)
|
| 273 |
if not existing_pet:
|
| 274 |
raise HTTPException(
|
| 275 |
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
| 283 |
)
|
| 284 |
|
| 285 |
# Delete pet from database
|
| 286 |
+
success = await PetModel.delete_pet(customer_id, pet_id)
|
| 287 |
if not success:
|
| 288 |
raise HTTPException(
|
| 289 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
app/schemas/address_schema.py
CHANGED
|
@@ -13,20 +13,12 @@ class AddressCreateRequest(BaseModel):
|
|
| 13 |
address_type: Literal["home", "work", "other"] = Field(default="home", description="Type of address")
|
| 14 |
is_default: bool = Field(default=False, description="Set as default address")
|
| 15 |
landmark: Optional[str] = Field("", max_length=200, description="Nearby landmark")
|
| 16 |
-
contact_name: Optional[str] = Field("", max_length=100, description="Contact person name")
|
| 17 |
-
contact_phone: Optional[str] = Field("", max_length=15, description="Contact phone number")
|
| 18 |
|
| 19 |
@validator('postal_code')
|
| 20 |
def validate_postal_code(cls, v):
|
| 21 |
if not v.isdigit():
|
| 22 |
raise ValueError('Postal code must contain only digits')
|
| 23 |
return v
|
| 24 |
-
|
| 25 |
-
@validator('contact_phone')
|
| 26 |
-
def validate_contact_phone(cls, v):
|
| 27 |
-
if v and not v.isdigit():
|
| 28 |
-
raise ValueError('Contact phone must contain only digits')
|
| 29 |
-
return v
|
| 30 |
|
| 31 |
class AddressUpdateRequest(BaseModel):
|
| 32 |
"""Request model for updating an existing address"""
|
|
@@ -39,20 +31,12 @@ class AddressUpdateRequest(BaseModel):
|
|
| 39 |
address_type: Optional[Literal["home", "work", "other"]] = Field(None, description="Type of address")
|
| 40 |
is_default: Optional[bool] = Field(None, description="Set as default address")
|
| 41 |
landmark: Optional[str] = Field(None, max_length=200, description="Nearby landmark")
|
| 42 |
-
contact_name: Optional[str] = Field(None, max_length=100, description="Contact person name")
|
| 43 |
-
contact_phone: Optional[str] = Field(None, max_length=15, description="Contact phone number")
|
| 44 |
|
| 45 |
@validator('postal_code')
|
| 46 |
def validate_postal_code(cls, v):
|
| 47 |
if v and not v.isdigit():
|
| 48 |
raise ValueError('Postal code must contain only digits')
|
| 49 |
return v
|
| 50 |
-
|
| 51 |
-
@validator('contact_phone')
|
| 52 |
-
def validate_contact_phone(cls, v):
|
| 53 |
-
if v and not v.isdigit():
|
| 54 |
-
raise ValueError('Contact phone must contain only digits')
|
| 55 |
-
return v
|
| 56 |
|
| 57 |
class AddressResponse(BaseModel):
|
| 58 |
"""Response model for address data"""
|
|
@@ -66,8 +50,6 @@ class AddressResponse(BaseModel):
|
|
| 66 |
address_type: str = Field(..., description="Type of address")
|
| 67 |
is_default: bool = Field(..., description="Is default address")
|
| 68 |
landmark: str = Field(..., description="Nearby landmark")
|
| 69 |
-
contact_name: str = Field(..., description="Contact person name")
|
| 70 |
-
contact_phone: str = Field(..., description="Contact phone number")
|
| 71 |
created_at: datetime = Field(..., description="Address creation timestamp")
|
| 72 |
updated_at: datetime = Field(..., description="Address last update timestamp")
|
| 73 |
|
|
|
|
| 13 |
address_type: Literal["home", "work", "other"] = Field(default="home", description="Type of address")
|
| 14 |
is_default: bool = Field(default=False, description="Set as default address")
|
| 15 |
landmark: Optional[str] = Field("", max_length=200, description="Nearby landmark")
|
|
|
|
|
|
|
| 16 |
|
| 17 |
@validator('postal_code')
|
| 18 |
def validate_postal_code(cls, v):
|
| 19 |
if not v.isdigit():
|
| 20 |
raise ValueError('Postal code must contain only digits')
|
| 21 |
return v
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
class AddressUpdateRequest(BaseModel):
|
| 24 |
"""Request model for updating an existing address"""
|
|
|
|
| 31 |
address_type: Optional[Literal["home", "work", "other"]] = Field(None, description="Type of address")
|
| 32 |
is_default: Optional[bool] = Field(None, description="Set as default address")
|
| 33 |
landmark: Optional[str] = Field(None, max_length=200, description="Nearby landmark")
|
|
|
|
|
|
|
| 34 |
|
| 35 |
@validator('postal_code')
|
| 36 |
def validate_postal_code(cls, v):
|
| 37 |
if v and not v.isdigit():
|
| 38 |
raise ValueError('Postal code must contain only digits')
|
| 39 |
return v
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
class AddressResponse(BaseModel):
|
| 42 |
"""Response model for address data"""
|
|
|
|
| 50 |
address_type: str = Field(..., description="Type of address")
|
| 51 |
is_default: bool = Field(..., description="Is default address")
|
| 52 |
landmark: str = Field(..., description="Nearby landmark")
|
|
|
|
|
|
|
| 53 |
created_at: datetime = Field(..., description="Address creation timestamp")
|
| 54 |
updated_at: datetime = Field(..., description="Address last update timestamp")
|
| 55 |
|
app/schemas/guest_schema.py
CHANGED
|
@@ -24,6 +24,7 @@ class GuestCreateRequest(BaseModel):
|
|
| 24 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth for age calculation")
|
| 25 |
relationship: Optional[RelationshipEnum] = Field(None, description="Relationship to the user")
|
| 26 |
notes: Optional[str] = Field(None, max_length=500, description="Additional notes about the guest")
|
|
|
|
| 27 |
|
| 28 |
@validator('first_name')
|
| 29 |
def validate_first_name(cls, v):
|
|
@@ -67,6 +68,7 @@ class GuestUpdateRequest(BaseModel):
|
|
| 67 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth for age calculation")
|
| 68 |
relationship: Optional[RelationshipEnum] = Field(None, description="Relationship to the user")
|
| 69 |
notes: Optional[str] = Field(None, max_length=500, description="Additional notes about the guest")
|
|
|
|
| 70 |
|
| 71 |
@validator('first_name')
|
| 72 |
def validate_first_name(cls, v):
|
|
@@ -112,6 +114,7 @@ class GuestResponse(BaseModel):
|
|
| 112 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth")
|
| 113 |
relationship: Optional[str] = Field(None, description="Relationship to the user")
|
| 114 |
notes: Optional[str] = Field(None, description="Additional notes about the guest")
|
|
|
|
| 115 |
created_at: datetime = Field(..., description="Guest profile creation timestamp")
|
| 116 |
updated_at: datetime = Field(..., description="Guest profile last update timestamp")
|
| 117 |
|
|
@@ -152,4 +155,8 @@ class GuestDeleteResponse(BaseModel):
|
|
| 152 |
guest_id: str = Field(..., description="ID of the deleted guest")
|
| 153 |
|
| 154 |
class Config:
|
| 155 |
-
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth for age calculation")
|
| 25 |
relationship: Optional[RelationshipEnum] = Field(None, description="Relationship to the user")
|
| 26 |
notes: Optional[str] = Field(None, max_length=500, description="Additional notes about the guest")
|
| 27 |
+
is_default: Optional[bool] = Field(None, description="Mark as default guest")
|
| 28 |
|
| 29 |
@validator('first_name')
|
| 30 |
def validate_first_name(cls, v):
|
|
|
|
| 68 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth for age calculation")
|
| 69 |
relationship: Optional[RelationshipEnum] = Field(None, description="Relationship to the user")
|
| 70 |
notes: Optional[str] = Field(None, max_length=500, description="Additional notes about the guest")
|
| 71 |
+
is_default: Optional[bool] = Field(None, description="Mark as default guest")
|
| 72 |
|
| 73 |
@validator('first_name')
|
| 74 |
def validate_first_name(cls, v):
|
|
|
|
| 114 |
date_of_birth: Optional[date] = Field(None, description="Guest's date of birth")
|
| 115 |
relationship: Optional[str] = Field(None, description="Relationship to the user")
|
| 116 |
notes: Optional[str] = Field(None, description="Additional notes about the guest")
|
| 117 |
+
is_default: bool = Field(..., description="Is default guest")
|
| 118 |
created_at: datetime = Field(..., description="Guest profile creation timestamp")
|
| 119 |
updated_at: datetime = Field(..., description="Guest profile last update timestamp")
|
| 120 |
|
|
|
|
| 155 |
guest_id: str = Field(..., description="ID of the deleted guest")
|
| 156 |
|
| 157 |
class Config:
|
| 158 |
+
from_attributes = True
|
| 159 |
+
|
| 160 |
+
class SetDefaultGuestRequest(BaseModel):
|
| 161 |
+
"""Request model for setting default guest"""
|
| 162 |
+
guest_id: str = Field(..., description="Guest ID to set as default")
|
app/schemas/pet_schema.py
CHANGED
|
@@ -60,6 +60,7 @@ class PetUpdateRequest(BaseModel):
|
|
| 60 |
health_notes: Optional[str] = Field(None, max_length=1000, description="Health notes, allergies, medications")
|
| 61 |
is_vaccinated: Optional[bool] = Field(None, description="Vaccination status")
|
| 62 |
pet_photo_url: Optional[str] = Field(None, max_length=500, description="URL to pet's photo")
|
|
|
|
| 63 |
|
| 64 |
@validator('pet_name')
|
| 65 |
def validate_pet_name(cls, v):
|
|
@@ -82,6 +83,7 @@ class PetResponse(BaseModel):
|
|
| 82 |
health_notes: Optional[str] = Field(None, description="Health notes, allergies, medications")
|
| 83 |
is_vaccinated: bool = Field(..., description="Vaccination status")
|
| 84 |
pet_photo_url: Optional[str] = Field(None, description="URL to pet's photo")
|
|
|
|
| 85 |
created_at: datetime = Field(..., description="Pet profile creation timestamp")
|
| 86 |
updated_at: datetime = Field(..., description="Pet profile last update timestamp")
|
| 87 |
|
|
@@ -105,4 +107,8 @@ class PetDeleteResponse(BaseModel):
|
|
| 105 |
pet_id: str = Field(..., description="ID of the deleted pet")
|
| 106 |
|
| 107 |
class Config:
|
| 108 |
-
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
health_notes: Optional[str] = Field(None, max_length=1000, description="Health notes, allergies, medications")
|
| 61 |
is_vaccinated: Optional[bool] = Field(None, description="Vaccination status")
|
| 62 |
pet_photo_url: Optional[str] = Field(None, max_length=500, description="URL to pet's photo")
|
| 63 |
+
is_default: Optional[bool] = Field(None, description="Mark as default pet")
|
| 64 |
|
| 65 |
@validator('pet_name')
|
| 66 |
def validate_pet_name(cls, v):
|
|
|
|
| 83 |
health_notes: Optional[str] = Field(None, description="Health notes, allergies, medications")
|
| 84 |
is_vaccinated: bool = Field(..., description="Vaccination status")
|
| 85 |
pet_photo_url: Optional[str] = Field(None, description="URL to pet's photo")
|
| 86 |
+
is_default: bool = Field(..., description="Is default pet")
|
| 87 |
created_at: datetime = Field(..., description="Pet profile creation timestamp")
|
| 88 |
updated_at: datetime = Field(..., description="Pet profile last update timestamp")
|
| 89 |
|
|
|
|
| 107 |
pet_id: str = Field(..., description="ID of the deleted pet")
|
| 108 |
|
| 109 |
class Config:
|
| 110 |
+
from_attributes = True
|
| 111 |
+
|
| 112 |
+
class SetDefaultPetRequest(BaseModel):
|
| 113 |
+
"""Request model for setting default pet"""
|
| 114 |
+
pet_id: str = Field(..., description="Pet ID to set as default")
|