rairo commited on
Commit
519780f
·
verified ·
1 Parent(s): d737e40

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +148 -1
main.py CHANGED
@@ -5,7 +5,7 @@ import uuid
5
  import re
6
  import json
7
  import traceback
8
- from datetime import datetime, timedelta
9
 
10
  from flask import Flask, request, jsonify
11
  from flask_cors import CORS
@@ -68,6 +68,56 @@ def is_valid_email(email):
68
  regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
69
  return re.match(regex, email) is not None
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # -----------------------------------------------------------------------------
72
  # 3. AUTHENTICATION & USER MANAGEMENT
73
  # -----------------------------------------------------------------------------
@@ -901,6 +951,103 @@ def get_feedback_details(feedback_id):
901
  traceback.print_exc()
902
  return jsonify({'error': str(e)}), 500
903
  # -----------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
  # 7. MAIN EXECUTION
905
  # -----------------------------------------------------------------------------
906
  if __name__ == '__main__':
 
5
  import re
6
  import json
7
  import traceback
8
+ from datetime import datetime, timedelta, timezone
9
 
10
  from flask import Flask, request, jsonify
11
  from flask_cors import CORS
 
68
  regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
69
  return re.match(regex, email) is not None
70
 
71
+ def _send_notification(user_id, user_email, message_content, send_email=False, email_subject=None, email_body=None):
72
+ """
73
+ Internal helper to send notifications.
74
+ Creates an in-app notification in Firebase and optionally sends an email via Resend.
75
+ If user_id is None, it will only attempt to send an email.
76
+ """
77
+ timestamp = datetime.now(timezone.utc).isoformat()
78
+
79
+ # 1. Send In-App Notification (if user_id is provided)
80
+ if user_id:
81
+ try:
82
+ notif_ref = db.reference(f'notifications/{user_id}').push()
83
+ notif_data = {
84
+ 'id': notif_ref.key,
85
+ 'message': message_content,
86
+ 'created_at': timestamp,
87
+ 'read': False,
88
+ 'read_at': None
89
+ }
90
+ notif_ref.set(notif_data)
91
+ logger.info(f"Successfully sent in-app notification to UID {user_id}")
92
+ except Exception as e:
93
+ logger.error(f"Failed to send in-app notification to UID {user_id}: {e}")
94
+ return False # Fail the whole operation if in-app fails for a registered user
95
+
96
+ # 2. Send Email via Resend (if requested)
97
+ if send_email and user_email:
98
+ if not RESEND_API_KEY:
99
+ logger.error("RESEND_API_KEY is not configured. Cannot send email.")
100
+ return False
101
+
102
+ headers = {
103
+ "Authorization": f"Bearer {RESEND_API_KEY}",
104
+ "Content-Type": "application/json"
105
+ }
106
+ payload = {
107
+ "from": "Sozo <onboarding@sozofix.tech>", # Replace with your verified Resend domain
108
+ "to": [user_email],
109
+ "subject": email_subject,
110
+ "html": email_body
111
+ }
112
+ try:
113
+ response = requests.post("https://api.resend.com/emails", json=payload)
114
+ response.raise_for_status()
115
+ logger.info(f"Successfully sent email to {user_email}")
116
+ except requests.exceptions.RequestException as e:
117
+ logger.error(f"Failed to send email to {user_email} via Resend: {e}")
118
+ return False
119
+
120
+ return True
121
  # -----------------------------------------------------------------------------
122
  # 3. AUTHENTICATION & USER MANAGEMENT
123
  # -----------------------------------------------------------------------------
 
951
  traceback.print_exc()
952
  return jsonify({'error': str(e)}), 500
953
  # -----------------------------------------------------------------------------
954
+ # 4. NOTIFICATION ENDPOINTS
955
+ # -----------------------------------------------------------------------------
956
+
957
+ @app.route('/api/admin/notifications/send', methods=['POST'])
958
+ def admin_send_notification():
959
+ logger.info("Endpoint /api/admin/notifications/send POST: Received request.")
960
+ try:
961
+ verify_admin(request.headers.get('Authorization'))
962
+
963
+ data = request.get_json()
964
+ message_content = data.get('message')
965
+ target_group = data.get('target_group', 'all')
966
+ target_users_list = data.get('target_users', [])
967
+
968
+ send_as_email = data.get('send_as_email', False)
969
+ email_subject = data.get('email_subject')
970
+ email_body_html = data.get('email_body_html')
971
+
972
+ if not message_content:
973
+ return jsonify({'error': 'In-app notification message is required'}), 400
974
+ if send_as_email and (not email_subject or not email_body_html):
975
+ return jsonify({'error': 'Email subject and body are required when sending as email.'}), 400
976
+
977
+ recipients = [] # List of tuples (uid, email)
978
+
979
+ if target_group == 'all':
980
+ all_users = db.reference('users').get() or {}
981
+ for uid, user_data in all_users.items():
982
+ recipients.append((uid, user_data.get('email')))
983
+ elif target_group == 'waitlist':
984
+ waitlist_users = db.reference('sozo_waitlist').get() or {}
985
+ for _, user_data in waitlist_users.items():
986
+ # For waitlist, UID is None as they are not registered users yet
987
+ recipients.append((None, user_data.get('email')))
988
+ elif target_users_list:
989
+ all_users = db.reference('users').get() or {}
990
+ for uid in target_users_list:
991
+ if uid in all_users:
992
+ recipients.append((uid, all_users[uid].get('email')))
993
+ else:
994
+ return jsonify({'error': 'Invalid target specified'}), 400
995
+
996
+ sent_count = 0
997
+ for uid_recipient, email_recipient in recipients:
998
+ if _send_notification(
999
+ user_id=uid_recipient,
1000
+ user_email=email_recipient,
1001
+ message_content=message_content,
1002
+ send_email=send_as_email,
1003
+ email_subject=email_subject,
1004
+ email_body=email_body_html
1005
+ ):
1006
+ sent_count += 1
1007
+
1008
+ return jsonify({'success': True, 'message': f"Notification dispatched for {sent_count} recipient(s)."}), 200
1009
+
1010
+ except PermissionError as e:
1011
+ return jsonify({'error': str(e)}), 403
1012
+ except Exception as e:
1013
+ logger.error(f"CRITICAL ERROR during notification send: {traceback.format_exc()}")
1014
+ return jsonify({'error': str(e)}), 500
1015
+
1016
+ @app.route('/api/user/notifications', methods=['GET'])
1017
+ def get_user_notifications():
1018
+ try:
1019
+ token = request.headers.get('Authorization', '').split(' ')[1]
1020
+ uid = verify_token(token)
1021
+ if not uid: return jsonify({'error': 'Unauthorized'}), 401
1022
+
1023
+ notifications_ref = db.reference(f'notifications/{uid}')
1024
+ user_notifications = notifications_ref.order_by_child('created_at').get() or {}
1025
+
1026
+ # Sort descending (newest first)
1027
+ sorted_notifications = sorted(user_notifications.values(), key=lambda item: item['created_at'], reverse=True)
1028
+
1029
+ return jsonify(sorted_notifications), 200
1030
+ except Exception as e:
1031
+ logger.error(f"CRITICAL ERROR getting notifications: {traceback.format_exc()}")
1032
+ return jsonify({'error': str(e)}), 500
1033
+
1034
+ @app.route('/api/user/notifications/<string:notification_id>/read', methods=['POST'])
1035
+ def mark_notification_read(notification_id):
1036
+ try:
1037
+ token = request.headers.get('Authorization', '').split(' ')[1]
1038
+ uid = verify_token(token)
1039
+ if not uid: return jsonify({'error': 'Unauthorized'}), 401
1040
+
1041
+ notif_ref = db.reference(f'notifications/{uid}/{notification_id}')
1042
+ if not notif_ref.get():
1043
+ return jsonify({'error': 'Notification not found'}), 404
1044
+
1045
+ notif_ref.update({'read': True, 'read_at': datetime.now(timezone.utc).isoformat()})
1046
+ return jsonify({'success': True, 'message': 'Notification marked as read.'}), 200
1047
+ except Exception as e:
1048
+ logger.error(f"CRITICAL ERROR marking notification read: {traceback.format_exc()}")
1049
+ return jsonify({'error': str(e)}), 500
1050
+ # -----------------------------------------------------------------------------
1051
  # 7. MAIN EXECUTION
1052
  # -----------------------------------------------------------------------------
1053
  if __name__ == '__main__':