rairo commited on
Commit
d8ff170
·
verified ·
1 Parent(s): d6f6172

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +42 -90
main.py CHANGED
@@ -177,70 +177,8 @@ def get_user_profile():
177
 
178
  return jsonify({'uid': uid, **user_doc.to_dict()})
179
 
180
- # dashboard_server.py
181
 
182
 
183
-
184
- @app.route('/api/user/profile/phone', methods=['PUT'])
185
- def update_user_phone():
186
- """
187
- Allows a user to submit their WhatsApp phone number for admin approval.
188
- **DEFINITIVE FIX**: Correctly handles the `datetime` object returned from Firestore,
189
- preventing the JSON serialization error that causes the MutationFn to fail.
190
- """
191
- uid = verify_token(request.headers.get('Authorization'))
192
- if not uid: return jsonify({'error': 'Invalid or expired token'}), 401
193
-
194
- data = request.get_json()
195
- phone_number = data.get('phone')
196
-
197
- if not phone_number or not isinstance(phone_number, str) or not phone_number.strip():
198
- return jsonify({'error': 'A valid phone number is required.'}), 400
199
-
200
- phone_number_stripped = phone_number.strip()
201
-
202
- try:
203
- # Validate uniqueness
204
- existing_user_query = db.collection('users').where('phone', '==', phone_number_stripped).limit(1).stream()
205
- conflicting_users = list(existing_user_query)
206
- if len(conflicting_users) > 0 and conflicting_users[0].id != uid:
207
- return jsonify({'error': 'This phone number is already registered to another account.'}), 409
208
-
209
- # Update the user's profile
210
- user_ref = db.collection('users').document(uid)
211
- user_ref.update({
212
- 'phone': phone_number_stripped,
213
- 'phoneStatus': 'pending'
214
- })
215
-
216
- logging.info(f"User {uid} submitted phone number {phone_number_stripped} for approval.")
217
-
218
- # --- THE FIX IS HERE ---
219
- # 1. Re-fetch the updated user document
220
- updated_user_doc = user_ref.get()
221
- if not updated_user_doc.exists:
222
- return jsonify({'error': 'Failed to retrieve updated profile.'}), 500
223
-
224
- # 2. Convert the document to a dictionary
225
- user_data = updated_user_doc.to_dict()
226
-
227
- # 3. IMPORTANT: Check for and convert any datetime objects to strings
228
- if 'createdAt' in user_data and isinstance(user_data['createdAt'], datetime):
229
- user_data['createdAt'] = user_data['createdAt'].isoformat() + "Z"
230
-
231
- # 4. Return the now JSON-safe data, with the correct top-level structure
232
- return jsonify({
233
- 'success': True,
234
- 'message': 'Phone number submitted for approval.',
235
- 'uid': uid,
236
- **user_data
237
- }), 200
238
- # --- END OF FIX ---
239
-
240
- except Exception as e:
241
- logging.error(f"Error updating phone for user {uid}: {e}", exc_info=True)
242
- return jsonify({'error': 'Failed to update phone number'}), 500
243
-
244
  @app.route('/api/user/dashboard', methods=['GET'])
245
  def get_user_dashboard():
246
  """
@@ -299,12 +237,13 @@ def get_user_dashboard():
299
  logging.error(f"Error fetching dashboard data for user {uid} (phone: {phone_number}): {e}")
300
  return jsonify({'error': 'An error occurred while fetching your dashboard data.'}), 500
301
 
 
 
302
  @app.route('/api/user/profile', methods=['PUT'])
303
  def update_user_profile():
304
  """
305
- Allows a user to update their profile. If the phone number is changed,
306
- it validates for uniqueness and stages the change based on user's choice
307
- (migrate or start fresh).
308
  """
309
  uid = verify_token(request.headers.get('Authorization'))
310
  if not uid: return jsonify({'error': 'Invalid or expired token'}), 401
@@ -318,63 +257,76 @@ def update_user_profile():
318
  if not data: return jsonify({'error': 'No data provided'}), 400
319
 
320
  update_data = {}
 
321
 
 
322
  new_display_name = data.get('displayName')
323
  if new_display_name and new_display_name.strip() != current_user_data.get('displayName'):
324
  update_data['displayName'] = new_display_name.strip()
325
 
326
- # --- REWORKED LOGIC FOR PHONE CHANGE ---
327
  new_phone = data.get('phone')
328
  if new_phone:
329
  new_phone_stripped = new_phone.strip()
330
  current_phone = current_user_data.get('phone')
331
 
332
- if new_phone_stripped != current_phone:
333
- # Get the user's choice: 'migrate' or 'start_fresh'
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  action = data.get('phoneChangeAction')
335
  if action not in ['migrate', 'start_fresh']:
336
- return jsonify({'error': "A choice ('migrate' or 'start_fresh') is required when changing phone numbers."}), 400
337
 
338
- # Validate that the new phone number is not already in use
339
  existing_user_query = db.collection('users').where('phone', '==', new_phone_stripped).limit(1).stream()
340
  if len(list(existing_user_query)) > 0:
341
  return jsonify({'error': 'This phone number is already registered to another account.'}), 409
342
 
343
  # Stage the change based on the user's chosen action
344
- update_data['migration_data'] = {
345
- 'from_phone': current_phone,
346
- 'to_phone': new_phone_stripped,
347
- 'action': action # Store the user's choice
348
- }
349
-
350
- # Use a descriptive status for the admin
351
- if action == 'migrate':
352
- update_data['phoneStatus'] = 'pending_migration'
353
- else: # action == 'start_fresh'
354
- update_data['phoneStatus'] = 'pending_fresh_start'
355
-
356
- logging.info(f"User {uid} initiated phone change to {new_phone_stripped} with action '{action}'. Awaiting admin approval.")
357
-
358
- # --- END OF REWORKED LOGIC ---
359
-
360
  if not update_data:
361
  return jsonify({'message': 'No changes detected.'}), 200
362
 
363
  try:
 
364
  user_ref.update(update_data)
365
 
 
366
  if 'displayName' in update_data:
367
  auth.update_user(uid, display_name=update_data['displayName'])
368
 
 
369
  updated_user_doc = user_ref.get()
370
- response_message = 'Profile updated successfully.'
371
- if 'phoneStatus' in update_data:
372
- response_message += ' Your request to change your phone number has been submitted for approval.'
 
 
373
 
 
 
 
 
374
  return jsonify({
375
  'success': True,
376
  'message': response_message,
377
- 'user': {'uid': uid, **updated_user_doc.to_dict()}
378
  }), 200
379
 
380
  except Exception as e:
 
177
 
178
  return jsonify({'uid': uid, **user_doc.to_dict()})
179
 
 
180
 
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  @app.route('/api/user/dashboard', methods=['GET'])
183
  def get_user_dashboard():
184
  """
 
237
  logging.error(f"Error fetching dashboard data for user {uid} (phone: {phone_number}): {e}")
238
  return jsonify({'error': 'An error occurred while fetching your dashboard data.'}), 500
239
 
240
+
241
+ # Replace your existing PUT /api/user/profile with this one.
242
  @app.route('/api/user/profile', methods=['PUT'])
243
  def update_user_profile():
244
  """
245
+ A single, intelligent endpoint to handle all user profile updates,
246
+ including initial phone submission and subsequent changes/migrations.
 
247
  """
248
  uid = verify_token(request.headers.get('Authorization'))
249
  if not uid: return jsonify({'error': 'Invalid or expired token'}), 401
 
257
  if not data: return jsonify({'error': 'No data provided'}), 400
258
 
259
  update_data = {}
260
+ response_message = "Profile updated successfully."
261
 
262
+ # --- Handle Display Name Update ---
263
  new_display_name = data.get('displayName')
264
  if new_display_name and new_display_name.strip() != current_user_data.get('displayName'):
265
  update_data['displayName'] = new_display_name.strip()
266
 
267
+ # --- Handle All Phone-Related Scenarios Intelligently ---
268
  new_phone = data.get('phone')
269
  if new_phone:
270
  new_phone_stripped = new_phone.strip()
271
  current_phone = current_user_data.get('phone')
272
 
273
+ # Scenario 1: New user submitting a phone for the first time
274
+ if not current_phone:
275
+ # Validate uniqueness
276
+ existing_user_query = db.collection('users').where('phone', '==', new_phone_stripped).limit(1).stream()
277
+ if len(list(existing_user_query)) > 0:
278
+ return jsonify({'error': 'This phone number is already registered to another account.'}), 409
279
+
280
+ # Stage the update for simple approval
281
+ update_data['phone'] = new_phone_stripped
282
+ update_data['phoneStatus'] = 'pending'
283
+ response_message += ' Your phone number has been submitted for approval.'
284
+ logging.info(f"New user {uid} submitted phone {new_phone_stripped} for initial approval.")
285
+
286
+ # Scenario 2: Existing user changing their phone number
287
+ elif new_phone_stripped != current_phone:
288
  action = data.get('phoneChangeAction')
289
  if action not in ['migrate', 'start_fresh']:
290
+ return jsonify({'error': "A choice ('migrate' or 'start_fresh') is required when changing an existing phone number."}), 400
291
 
292
+ # Validate uniqueness of the new number
293
  existing_user_query = db.collection('users').where('phone', '==', new_phone_stripped).limit(1).stream()
294
  if len(list(existing_user_query)) > 0:
295
  return jsonify({'error': 'This phone number is already registered to another account.'}), 409
296
 
297
  # Stage the change based on the user's chosen action
298
+ update_data['migration_data'] = {'from_phone': current_phone, 'to_phone': new_phone_stripped, 'action': action}
299
+ update_data['phoneStatus'] = 'pending_migration' if action == 'migrate' else 'pending_fresh_start'
300
+ response_message += ' Your request to change your phone number has been submitted for approval.'
301
+ logging.info(f"User {uid} initiated phone change to {new_phone_stripped} with action '{action}'.")
302
+
 
 
 
 
 
 
 
 
 
 
 
303
  if not update_data:
304
  return jsonify({'message': 'No changes detected.'}), 200
305
 
306
  try:
307
+ # Commit all staged changes to Firestore
308
  user_ref.update(update_data)
309
 
310
+ # Also update Firebase Auth display name if it was changed
311
  if 'displayName' in update_data:
312
  auth.update_user(uid, display_name=update_data['displayName'])
313
 
314
+ # --- Create a Clean, JSON-Safe Response ---
315
  updated_user_doc = user_ref.get()
316
+ final_user_data = updated_user_doc.to_dict()
317
+
318
+ # Convert datetime objects to strings
319
+ if 'createdAt' in final_user_data and isinstance(final_user_data['createdAt'], datetime):
320
+ final_user_data['createdAt'] = final_user_data['createdAt'].isoformat() + "Z"
321
 
322
+ # Do not send internal migration data to the frontend
323
+ if 'migration_data' in final_user_data:
324
+ del final_user_data['migration_data']
325
+
326
  return jsonify({
327
  'success': True,
328
  'message': response_message,
329
+ **final_user_data # Return the clean, flattened user object
330
  }), 200
331
 
332
  except Exception as e: