rairo commited on
Commit
9b151b7
·
verified ·
1 Parent(s): 0fb2958

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +130 -31
main.py CHANGED
@@ -47,6 +47,19 @@ else:
47
  logger.error(f"Failed to initialize Gemini AI Client with genai.Client(): {e}")
48
  gemini_client = None
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  #--- Firebase Initialization ---
51
  FIREBASE_CREDENTIALS_JSON_STRING = os.getenv("FIREBASE")
52
  FIREBASE_DB_URL = os.getenv("Firebase_DB")
@@ -223,14 +236,17 @@ def handle_route_errors(e, uid_context="unknown"): # Added uid_context for bette
223
  return jsonify({'error': f'An unexpected error occurred: {str(e)}', 'type': 'GenericError'}), 500
224
 
225
  #--- system notifications ---
226
- def _send_system_notification(user_id, message_content, notif_type, link=None): # Renamed message to message_content
 
227
  if not FIREBASE_INITIALIZED:
228
  logger.error("_send_system_notification: Firebase not ready.")
229
  return False
230
  if not user_id or not message_content:
231
- logger.warning(f"_send_system_notification: Called with missing user_id ('{user_id}') or message_content ('{message_content}').")
232
  return False
233
 
 
 
234
  notif_id = str(uuid.uuid4())
235
  notif_data = {
236
  "message": message_content,
@@ -241,14 +257,50 @@ def _send_system_notification(user_id, message_content, notif_type, link=None):
241
  }
242
  try:
243
  db.reference(f'notifications/{user_id}/{notif_id}', app=db_app).set(notif_data)
244
- logger.info(f"Notification sent to {user_id}: {message_content[:50]}...")
245
- return True
246
  except firebase_exceptions.FirebaseError as fe:
247
- logger.error(f"Failed to send notification to {user_id} due to Firebase error: {fe}")
248
- return False
249
  except Exception as e:
250
- logger.error(f"Failed to send notification to {user_id} due to generic error: {e}")
251
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
  #--- Authentication Endpoints ---
254
  @app.route('/api/auth/signup', methods=['POST'])
@@ -518,28 +570,45 @@ def admin_action_on_role(application_id, action):
518
  message_text = ""
519
 
520
  if action == 'approve':
521
- app_ref.update({
522
- 'status': 'approved',
523
- 'reviewed_by': admin_uid,
524
- 'reviewed_at': update_time
525
- })
526
  user_profile_ref.child('roles').update({role: True})
527
  user_profile_ref.child('role_applications').update({role: 'approved'})
528
  message_text = f"Role {role} for user {user_id} approved."
 
529
  else: # reject
530
- app_ref.update({
531
- 'status': 'rejected',
532
- 'reviewed_by': admin_uid,
533
- 'reviewed_at': update_time
534
- })
535
  user_profile_ref.child('role_applications').update({role: 'rejected'})
536
  message_text = f"Role {role} for user {user_id} rejected."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
 
538
  _send_system_notification(
539
- user_id,
540
- f"Your application for the '{role}' role has been {action}d.",
541
- "role_status",
542
- f"/profile/roles"
 
 
 
543
  )
544
 
545
  return jsonify({'success': True, 'message': message_text}), 200
@@ -1905,25 +1974,55 @@ def admin_send_notification():
1905
  try:
1906
  admin_uid = verify_admin(auth_header)
1907
  if not FIREBASE_INITIALIZED: return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 503
 
1908
  data = request.get_json()
1909
- message_content, target_group, target_users_list = data.get('message'), data.get('target_group', 'all'), data.get('target_users', [])
1910
- if not message_content: return jsonify({'error': 'Message is required'}), 400
 
 
 
 
 
 
 
 
 
 
 
 
1911
  recipients_uids = set()
1912
  all_users_data = db.reference('users', app=db_app).get() or {}
 
1913
  if target_users_list and isinstance(target_users_list, list):
1914
  for uid_target in target_users_list:
1915
  if uid_target in all_users_data: recipients_uids.add(uid_target)
1916
- elif target_group == 'all': recipients_uids.update(all_users_data.keys())
 
1917
  elif target_group in ['farmers', 'buyers', 'transporters']:
1918
- role_key = target_group[:-1]
1919
  for uid_loop, u_data in all_users_data.items():
1920
- if u_data and u_data.get('roles', {}).get(role_key, False): recipients_uids.add(uid_loop)
1921
- else: return jsonify({'error': 'Invalid target_group or target_users not provided correctly'}), 400
 
 
 
1922
  sent_count = 0
1923
  for uid_recipient in recipients_uids:
1924
- if _send_system_notification(uid_recipient, message_content, "admin_broadcast"): sent_count += 1
1925
- return jsonify({'success': True, 'message': f"Broadcast notification sent to {sent_count} user(s)."}), 200
1926
- except Exception as e: return handle_route_errors(e, uid_context=admin_uid)
 
 
 
 
 
 
 
 
 
 
 
 
1927
 
1928
  @app.route('/api/user/notifications', methods=['GET'])
1929
  def get_user_notifications():
 
47
  logger.error(f"Failed to initialize Gemini AI Client with genai.Client(): {e}")
48
  gemini_client = None
49
 
50
+ import resend
51
+ # NEW: Initialize the Resend client using environment variables
52
+ # This should be placed near your other initializations at the top of the file.
53
+ if 'RESEND_API_KEY' in os.environ:
54
+ resend.api_key = os.environ["RESEND_API_KEY"]
55
+ SENDER_EMAIL = os.environ.get("SENDER_EMAIL")
56
+ if not SENDER_EMAIL:
57
+ logger.warning("RESEND_API_KEY is set, but SENDER_EMAIL is not. Emails will not be sent.")
58
+ resend.api_key = None # Disable client if sender is not configured
59
+ else:
60
+ logger.info("RESEND_API_KEY environment variable not found. Email notifications will be disabled.")
61
+ resend.api_key = None
62
+
63
  #--- Firebase Initialization ---
64
  FIREBASE_CREDENTIALS_JSON_STRING = os.getenv("FIREBASE")
65
  FIREBASE_DB_URL = os.getenv("Firebase_DB")
 
236
  return jsonify({'error': f'An unexpected error occurred: {str(e)}', 'type': 'GenericError'}), 500
237
 
238
  #--- system notifications ---
239
+
240
+ def _send_system_notification(user_id, message_content, notif_type, link=None, send_email=False, email_subject=None, email_body=None):
241
  if not FIREBASE_INITIALIZED:
242
  logger.error("_send_system_notification: Firebase not ready.")
243
  return False
244
  if not user_id or not message_content:
245
+ logger.warning(f"_send_system_notification: Called with missing user_id or message_content.")
246
  return False
247
 
248
+ # --- Primary Channel: Firebase In-App Notification ---
249
+ firebase_success = False
250
  notif_id = str(uuid.uuid4())
251
  notif_data = {
252
  "message": message_content,
 
257
  }
258
  try:
259
  db.reference(f'notifications/{user_id}/{notif_id}', app=db_app).set(notif_data)
260
+ logger.info(f"Firebase notification sent to {user_id}: {message_content[:50]}...")
261
+ firebase_success = True
262
  except firebase_exceptions.FirebaseError as fe:
263
+ logger.error(f"Failed to send Firebase notification to {user_id} due to Firebase error: {fe}")
 
264
  except Exception as e:
265
+ logger.error(f"Failed to send Firebase notification to {user_id} due to generic error: {e}")
266
+
267
+ # --- Secondary Channel: Email via Resend ---
268
+ if send_email:
269
+ if not resend.api_key or not SENDER_EMAIL:
270
+ logger.warning(f"Skipping email for user {user_id} because Resend is not configured.")
271
+ return firebase_success # Return status of primary channel
272
+
273
+ try:
274
+ user_profile = db.reference(f'users/{user_id}', app=db_app).get()
275
+ if not user_profile or not user_profile.get('email'):
276
+ logger.warning(f"Cannot send email to user {user_id}: no profile or email address found.")
277
+ return firebase_success
278
+
279
+ recipient_email = user_profile['email']
280
+ # Basic email validation
281
+ if '@' not in recipient_email or '.' not in recipient_email.split('@')[1]:
282
+ logger.warning(f"Cannot send email to user {user_id}: invalid email format ('{recipient_email}').")
283
+ return firebase_success
284
+
285
+ # Use message_content as fallback for email body if not provided
286
+ html_content = email_body if email_body else f"<p>{message_content}</p>"
287
+
288
+ params = {
289
+ "from": SENDER_EMAIL,
290
+ "to": [recipient_email],
291
+ "subject": email_subject or "New Notification from Tunasonga Agri",
292
+ "html": html_content,
293
+ }
294
+ email_response = resend.Emails.send(params)
295
+ logger.info(f"Email dispatched to {recipient_email} via Resend. ID: {email_response['id']}")
296
+
297
+ except firebase_exceptions.FirebaseError as fe_db:
298
+ logger.error(f"Email dispatch failed for {user_id}: Could not fetch user profile from Firebase. Error: {fe_db}")
299
+ except Exception as e_resend:
300
+ # This catches errors from the resend.Emails.send() call
301
+ logger.error(f"Email dispatch failed for {user_id} ({recipient_email}). Resend API Error: {e_resend}")
302
+
303
+ return firebase_success
304
 
305
  #--- Authentication Endpoints ---
306
  @app.route('/api/auth/signup', methods=['POST'])
 
570
  message_text = ""
571
 
572
  if action == 'approve':
573
+ app_ref.update({'status': 'approved', 'reviewed_by': admin_uid, 'reviewed_at': update_time})
 
 
 
 
574
  user_profile_ref.child('roles').update({role: True})
575
  user_profile_ref.child('role_applications').update({role: 'approved'})
576
  message_text = f"Role {role} for user {user_id} approved."
577
+ action_past_tense = "approved"
578
  else: # reject
579
+ app_ref.update({'status': 'rejected', 'reviewed_by': admin_uid, 'reviewed_at': update_time})
 
 
 
 
580
  user_profile_ref.child('role_applications').update({role: 'rejected'})
581
  message_text = f"Role {role} for user {user_id} rejected."
582
+ action_past_tense = "rejected"
583
+
584
+ # --- MODIFIED: Send a rich HTML email for this critical event ---
585
+ email_subject = f"Your Tunasonga Agri Application for the '{role.capitalize()}' Role"
586
+ email_body = f"""
587
+ <div style="font-family: sans-serif; padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px;">
588
+ <h2 style="color: #333;">Application Status Update</h2>
589
+ <p>Hello,</p>
590
+ <p>This is an update regarding your application for the <strong>{role.capitalize()}</strong> role on the Tunasonga platform.</p>
591
+ <p>Your application has been <strong>{action_past_tense}</strong> by an administrator.</p>
592
+ <a href="https://tunasongaagri.co.zw/profile" style="display: inline-block; padding: 10px 15px; background-color: #28a745; color: #ffffff; text-decoration: none; border-radius: 5px; margin-top: 15px;">
593
+ View My Profile
594
+ </a>
595
+ <p style="margin-top: 20px; font-size: 0.9em; color: #777;">
596
+ If you have any questions, please contact our support team.
597
+ </p>
598
+ <p style="margin-top: 5px; font-size: 0.9em; color: #777;">
599
+ Thank you,<br>The Tunasonga Agri Team
600
+ </p>
601
+ </div>
602
+ """
603
 
604
  _send_system_notification(
605
+ user_id=user_id,
606
+ message_content=f"Your application for the '{role}' role has been {action_past_tense}.",
607
+ notif_type="role_status",
608
+ link="/profile/roles",
609
+ send_email=True,
610
+ email_subject=email_subject,
611
+ email_body=email_body
612
  )
613
 
614
  return jsonify({'success': True, 'message': message_text}), 200
 
1974
  try:
1975
  admin_uid = verify_admin(auth_header)
1976
  if not FIREBASE_INITIALIZED: return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 503
1977
+
1978
  data = request.get_json()
1979
+ message_content = data.get('message') # For the in-app notification
1980
+ target_group = data.get('target_group', 'all')
1981
+ target_users_list = data.get('target_users', [])
1982
+
1983
+ # New email-specific fields from frontend
1984
+ send_as_email = data.get('send_as_email', False)
1985
+ email_subject = data.get('email_subject')
1986
+ email_body_html = data.get('email_body_html')
1987
+
1988
+ if not message_content:
1989
+ return jsonify({'error': 'In-app notification message is required'}), 400
1990
+ if send_as_email and (not email_subject or not email_body_html):
1991
+ return jsonify({'error': 'If sending as email, email_subject and email_body_html are required.'}), 400
1992
+
1993
  recipients_uids = set()
1994
  all_users_data = db.reference('users', app=db_app).get() or {}
1995
+
1996
  if target_users_list and isinstance(target_users_list, list):
1997
  for uid_target in target_users_list:
1998
  if uid_target in all_users_data: recipients_uids.add(uid_target)
1999
+ elif target_group == 'all':
2000
+ recipients_uids.update(all_users_data.keys())
2001
  elif target_group in ['farmers', 'buyers', 'transporters']:
2002
+ role_key = target_group[:-1] # 'farmers' -> 'farmer'
2003
  for uid_loop, u_data in all_users_data.items():
2004
+ if u_data and u_data.get('roles', {}).get(role_key, False):
2005
+ recipients_uids.add(uid_loop)
2006
+ else:
2007
+ return jsonify({'error': 'Invalid target_group or target_users not provided correctly'}), 400
2008
+
2009
  sent_count = 0
2010
  for uid_recipient in recipients_uids:
2011
+ # Call the upgraded notification function with all parameters
2012
+ if _send_system_notification(
2013
+ user_id=uid_recipient,
2014
+ message_content=message_content,
2015
+ notif_type="admin_broadcast",
2016
+ send_email=send_as_email,
2017
+ email_subject=email_subject,
2018
+ email_body=email_body_html
2019
+ ):
2020
+ sent_count += 1
2021
+
2022
+ return jsonify({'success': True, 'message': f"Broadcast notification dispatched for {sent_count} user(s)."}), 200
2023
+
2024
+ except Exception as e:
2025
+ return handle_route_errors(e, uid_context=admin_uid)
2026
 
2027
  @app.route('/api/user/notifications', methods=['GET'])
2028
  def get_user_notifications():