MukeshKapoor25 commited on
Commit
0f81784
·
1 Parent(s): 3866b57

feat(profiles): add default pet/guest functionality and refactor address model

Browse files

Implement 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 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
- "customer_id": customer_id,
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
- "contact_name": address_data.get("contact_name", ""),
31
- "contact_phone": address_data.get("contact_phone", ""),
32
- "created_at": datetime.utcnow(),
33
- "updated_at": datetime.utcnow()
34
  }
35
-
36
- # If this is set as default, unset other default addresses
37
- if address_doc["is_default"]:
38
- await AddressModel.collection.update_many(
39
- {"customer_id": customer_id, "is_default": True},
40
- {"$set": {"is_default": False, "updated_at": datetime.utcnow()}}
41
- )
42
-
43
- result = await AddressModel.collection.insert_one(address_doc)
44
- logger.info(f"Created address for user {customer_id}")
45
- return str(result.inserted_id)
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
- cursor = AddressModel.collection.find({"customer_id": customer_id}).sort("created_at", -1)
56
- addresses = []
57
-
58
- async for address in cursor:
59
- address["_id"] = str(address["_id"])
60
- addresses.append(address)
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
- address = await AddressModel.collection.find_one({
73
- "_id": ObjectId(address_id),
74
- "customer_id": customer_id
75
- })
76
-
77
- if address:
78
- address["_id"] = str(address["_id"])
79
-
80
- return address
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
- # Prepare update data
91
- update_fields = {}
92
- allowed_fields = [
93
- "address_line_1", "address_line_2", "city", "state", "postal_code",
94
- "country", "address_type", "is_default", "landmark", "contact_name", "contact_phone"
95
- ]
96
-
97
- for field in allowed_fields:
98
- if field in update_data:
99
- update_fields[field] = update_data[field]
100
-
101
- update_fields["updated_at"] = datetime.utcnow()
102
-
103
- # If setting as default, unset other default addresses
104
- if update_fields.get("is_default"):
105
- await AddressModel.collection.update_many(
106
- {"customer_id": customer_id, "is_default": True, "_id": {"$ne": ObjectId(address_id)}},
107
- {"$set": {"is_default": False, "updated_at": datetime.utcnow()}}
108
- )
109
-
110
- result = await AddressModel.collection.update_one(
111
- {"_id": ObjectId(address_id), "customer_id": customer_id},
112
- {"$set": update_fields}
 
 
 
 
 
 
 
 
 
 
 
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
- result = await AddressModel.collection.delete_one({
126
- "_id": ObjectId(address_id),
127
- "customer_id": customer_id
128
- })
129
-
130
- logger.info(f"Deleted address {address_id} for user {customer_id}")
131
- return result.deleted_count > 0
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
- address = await AddressModel.collection.find_one({
142
- "customer_id": customer_id,
143
- "is_default": True
144
- })
145
-
146
- if address:
147
- address["_id"] = str(address["_id"])
148
-
149
- return address
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
- # First, unset all default addresses for the user
160
- await AddressModel.collection.update_many(
161
- {"customer_id": customer_id, "is_default": True},
162
- {"$set": {"is_default": False, "updated_at": datetime.utcnow()}}
163
- )
164
-
165
- # Then set the specified address as default
166
- result = await AddressModel.collection.update_one(
167
- {"_id": ObjectId(address_id), "customer_id": customer_id},
168
- {"$set": {"is_default": True, "updated_at": datetime.utcnow()}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  )
170
-
171
- return result.modified_count > 0
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 in the database"""
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
- guests_collection = db.guests
35
-
 
 
 
 
36
  guest_id = str(uuid.uuid4())
37
  current_time = datetime.utcnow()
38
- print("gusest_data",guest_data)
39
-
40
- guest_data = {
41
  "guest_id": guest_id,
42
- "customer_id": customer_id,
43
- "first_name": guest_data["first_name"],
44
- "last_name": guest_data["last_name"],
45
- "email": guest_data["email"],
46
- "phone_number": guest_data["phone_number"],
47
- "gender": guest_data["gender"].value,
48
- "date_of_birth": guest_data["date_of_birth"],
49
- "relationship": guest_data['relationship'].value,
50
- "notes": guest_data['notes'],
51
  "created_at": current_time,
52
- "updated_at": current_time
53
  }
54
 
55
- guest_data = prepare_for_db(guest_data)
56
-
57
- result = await guests_collection.insert_one(guest_data)
58
-
59
- if result.inserted_id:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  logger.info(f"Guest created successfully: {guest_id} for user: {customer_id}")
61
  return guest_id
62
  else:
63
- logger.error(f"Failed to create guest for user: {customer_id}")
64
- return None
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
- guests_collection = db.guests
83
-
84
- cursor = guests_collection.find({"customer_id": customer_id})
85
- guests = await cursor.to_list(length=None)
86
-
87
- # Remove MongoDB's _id field
88
- for guest in guests:
89
- guest.pop("_id", None)
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
- guests_collection = db.guests
111
-
112
- guest = await guests_collection.find_one({"guest_id": guest_id})
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
- guests_collection = db.guests
140
-
141
- # Add updated timestamp
142
- update_fields["updated_at"] = datetime.utcnow()
143
 
144
- guest_data = prepare_for_db(update_fields)
145
-
146
- result = await guests_collection.update_one(
147
- {"guest_id": guest_id},
148
- {"$set": guest_data}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  )
150
-
151
  if result.modified_count > 0:
152
- logger.info(f"Guest updated successfully: {guest_id}")
153
  return True
154
  else:
155
- logger.warning(f"No changes made to guest: {guest_id}")
156
- return False
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
- guests_collection = db.guests
175
-
176
- result = await guests_collection.delete_one({"guest_id": guest_id})
177
-
178
- if result.deleted_count > 0:
179
- logger.info(f"Guest deleted successfully: {guest_id}")
 
 
 
 
 
 
 
 
 
 
 
 
180
  return True
181
  else:
182
- logger.warning(f"Guest not found for deletion: {guest_id}")
183
- return False
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
- guests_collection = db.guests
202
-
203
- count = await guests_collection.count_documents({"customer_id": customer_id})
204
- return count
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
- guests_collection = db.guests
224
-
225
- guest = await guests_collection.find_one({
226
- "guest_id": guest_id,
227
- "customer_id": customer_id
228
- })
229
-
230
- return guest is not None
231
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  except Exception as e:
233
- logger.error(f"Error checking guest ownership {guest_id} for user {customer_id}: {str(e)}")
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 in the database"""
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
- pets_collection = db.pets
41
-
 
 
 
 
42
  pet_id = str(uuid.uuid4())
43
  current_time = datetime.utcnow()
44
 
45
-
46
- pet_data = {
47
  "pet_id": pet_id,
48
- "customer_id": customer_id,
49
- "pet_name": pet_data['pet_name'],
50
- "species": pet_data['species'].value,
51
- "breed": pet_data['breed'],
52
- "date_of_birth": pet_data['date_of_birth'],
53
- "age": pet_data['age'],
54
- "weight": pet_data['weight'],
55
- "gender": pet_data['gender'].value,
56
- "temperament": pet_data['temperament'].value,
57
- "health_notes": pet_data['health_notes'],
58
- "is_vaccinated": pet_data['is_vaccinated'],
59
- "pet_photo_url": pet_data['pet_photo_url'],
60
  "created_at": current_time,
61
  "updated_at": current_time
62
  }
63
 
64
- pet_data = prepare_for_db(pet_data)
65
-
66
- result = await pets_collection.insert_one(pet_data)
67
-
68
- if result.inserted_id:
69
- logger.info(f"Pet created successfully: {pet_id} for user: {customer_id}")
 
 
 
 
 
 
 
 
 
 
 
 
70
  return pet_id
71
  else:
72
- logger.error(f"Failed to create pet for user: {customer_id}")
73
- return None
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
- pets_collection = db.pets
92
-
93
- cursor = pets_collection.find({"customer_id": customer_id})
94
- pets = await cursor.to_list(length=None)
95
-
96
- # Remove MongoDB's _id field
97
- for pet in pets:
98
- pet.pop("_id", None)
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
- pets_collection = db.pets
120
-
121
- pet = await pets_collection.find_one({"pet_id": pet_id})
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
- pets_collection = db.pets
149
-
150
- # Add updated timestamp
151
- update_fields["updated_at"] = datetime.utcnow()
152
-
153
- pet_data = prepare_for_db(update_fields)
154
-
155
- result = await pets_collection.update_one(
156
- {"pet_id": pet_id},
157
- {"$set": pet_data}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  )
159
-
160
  if result.modified_count > 0:
161
- logger.info(f"Pet updated successfully: {pet_id}")
162
  return True
163
  else:
164
- logger.warning(f"No changes made to pet: {pet_id}")
165
- return False
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
- pets_collection = db.pets
184
-
185
- result = await pets_collection.delete_one({"pet_id": pet_id})
186
-
187
- if result.deleted_count > 0:
188
- logger.info(f"Pet deleted successfully: {pet_id}")
 
 
 
 
 
 
 
 
 
 
 
 
189
  return True
190
  else:
191
- logger.warning(f"Pet not found for deletion: {pet_id}")
192
- return False
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
- pets_collection = db.pets
211
-
212
- count = await pets_collection.count_documents({"customer_id": customer_id})
213
- return count
214
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  except Exception as e:
216
- logger.error(f"Error counting pets for user {customer_id}: {str(e)}")
217
- return 0
 
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["_id"],
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["country"],
42
- landmark=addr["landmark"],
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["country"],
111
  is_default=created_address.get("is_default", False),
112
- landmark=created_address["landmark"],
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["country"],
202
- landmark=updated_address["landmark"],
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
- other_addresses = [addr for addr in user_addresses if addr["_id"] != address_id]
 
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]["_id"])
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["contact_name"],
309
- contact_phone=updated_address["contact_phone"],
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["country"],
316
- landmark=updated_address["landmark"],
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")