diff --git a/api/contact/contact_service.py b/api/contact/contact_service.py index 5187604..c99226c 100644 --- a/api/contact/contact_service.py +++ b/api/contact/contact_service.py @@ -120,6 +120,55 @@ def verify_recaptcha(token: str) -> bool: logger.exception("Error verifying recaptcha: %s", str(e)) return False +def get_all_contact_submissions() -> Dict[str, Any]: + """ + Get all contact form submissions for admin viewing. + + Returns: + Dict with list of submissions sorted by timestamp desc + """ + try: + db = get_db() + submissions = [] + docs = db.collection('contact_submissions').order_by('timestamp', direction='DESCENDING').stream() + for doc in docs: + data = doc.to_dict() + data['id'] = doc.id + submissions.append(data) + return {"success": True, "submissions": submissions} + except Exception as e: + logger.error("Error fetching contact submissions: %s", str(e)) + return {"success": False, "error": str(e), "submissions": []} + + +def admin_update_contact_submission(doc_id: str, data: Dict[str, Any]) -> Dict[str, Any]: + """ + Update a contact submission's status and admin notes. + + Args: + doc_id: Firestore document ID + data: Dict with status and/or adminNotes + + Returns: + Dict with success status + """ + try: + db = get_db() + update_data = {} + if 'status' in data: + update_data['status'] = data['status'] + if 'adminNotes' in data: + update_data['adminNotes'] = data['adminNotes'] + update_data['updatedAt'] = _get_current_timestamp() + + db.collection('contact_submissions').document(doc_id).update(update_data) + logger.info("Updated contact submission %s", doc_id) + return {"success": True} + except Exception as e: + logger.error("Error updating contact submission %s: %s", doc_id, str(e)) + return {"success": False, "error": str(e)} + + def send_confirmation_email(contact_data) -> bool: """ Send a confirmation email to the contact form submitter. diff --git a/api/contact/contact_views.py b/api/contact/contact_views.py index eacc332..722871f 100644 --- a/api/contact/contact_views.py +++ b/api/contact/contact_views.py @@ -1,7 +1,12 @@ from flask import Blueprint, jsonify, request from common.log import get_logger from common.exceptions import InvalidInputError -from api.contact.contact_service import submit_contact_form +from common.auth import auth, auth_user +from api.contact.contact_service import ( + submit_contact_form, + get_all_contact_submissions, + admin_update_contact_submission, +) logger = get_logger(__name__) bp = Blueprint('contact', __name__, url_prefix='/api') @@ -87,4 +92,40 @@ def handle_contact_form(): return jsonify({ "success": False, "error": "An error occurred while processing your request" - }), 500 \ No newline at end of file + }), 500 + + +def getOrgId(req): + return req.headers.get("X-Org-Id") + + +@bp.route("/contact/submissions", methods=["GET"]) +@auth.require_org_member_with_permission("volunteer.admin", req_to_org_id=getOrgId) +def admin_list_contact_submissions(): + """Admin endpoint to list all contact form submissions.""" + try: + result = get_all_contact_submissions() + if result.get('success'): + return jsonify(result), 200 + return jsonify(result), 500 + except Exception as e: + logger.exception("Error listing contact submissions: %s", str(e)) + return jsonify({"success": False, "error": str(e)}), 500 + + +@bp.route("/contact/submissions/", methods=["PATCH"]) +@auth.require_org_member_with_permission("volunteer.admin", req_to_org_id=getOrgId) +def admin_update_contact_submission_route(submission_id): + """Admin endpoint to update a contact submission's status and notes.""" + try: + data = request.get_json() + if not data: + return jsonify({"success": False, "error": "Empty request body"}), 400 + + result = admin_update_contact_submission(submission_id, data) + if result.get('success'): + return jsonify(result), 200 + return jsonify(result), 500 + except Exception as e: + logger.exception("Error updating contact submission: %s", str(e)) + return jsonify({"success": False, "error": str(e)}), 500 \ No newline at end of file diff --git a/api/volunteers/volunteers_views.py b/api/volunteers/volunteers_views.py index 8c8fc8d..bc12792 100644 --- a/api/volunteers/volunteers_views.py +++ b/api/volunteers/volunteers_views.py @@ -596,6 +596,8 @@ def admin_send_email_to_address(): recipient_type = request_data.get('recipient_type', 'volunteer') name = request_data.get('name') volunteer_id = request_data.get('volunteer_id') + collection_name = request_data.get('collection_name') + document_id = request_data.get('document_id') if not email: return _error_response("Email address is required", 400) @@ -613,7 +615,9 @@ def admin_send_email_to_address(): admin_user=auth_user, recipient_type=recipient_type, name=name, - volunteer_id=volunteer_id + volunteer_id=volunteer_id, + collection_name=collection_name, + document_id=document_id ) if result['success']: diff --git a/services/volunteers_service.py b/services/volunteers_service.py index cd1a923..6ea4eb4 100644 --- a/services/volunteers_service.py +++ b/services/volunteers_service.py @@ -1835,7 +1835,9 @@ def send_email_to_address( admin_user: Any = None, recipient_type: str = 'volunteer', name: Optional[str] = None, - volunteer_id: Optional[str] = None + volunteer_id: Optional[str] = None, + collection_name: Optional[str] = None, + document_id: Optional[str] = None ) -> Dict[str, Any]: """ Send an email to a specific email address using the same template as send_volunteer_message. @@ -1910,6 +1912,36 @@ def send_email_to_address( volunteer_id=volunteer_id, resend_email_id=resend_email_id, exc_info=tracking_error) + # Track email on any Firestore collection/document (generic tracking) + if collection_name and document_id and email_success: + try: + from db.db import get_db + db = get_db() + email_subject = f"{subject} - Message from Opportunity Hack Team" + + sent_email_record = { + 'resend_id': resend_email_id, + 'subject': email_subject, + 'timestamp': _get_current_timestamp(), + 'sent_by': admin_full_name, + 'recipient_type': recipient_type, + } + + doc_ref = db.collection(collection_name).document(document_id) + doc_ref.update({ + 'sent_emails': firestore.ArrayUnion([sent_email_record]), + 'last_email_timestamp': _get_current_timestamp(), + }) + + info(logger, "Updated document with sent_emails tracking", + collection=collection_name, document_id=document_id, + resend_email_id=resend_email_id) + + except Exception as tracking_error: + error(logger, "Failed to update document with sent_emails tracking", + collection=collection_name, document_id=document_id, + resend_email_id=resend_email_id, exc_info=tracking_error) + # Enhanced Slack audit message from common.utils.slack import send_slack_audit message_preview = message[:100] + "..." if len(message) > 100 else message