Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions api/contact/contact_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 43 additions & 2 deletions api/contact/contact_views.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -87,4 +92,40 @@ def handle_contact_form():
return jsonify({
"success": False,
"error": "An error occurred while processing your request"
}), 500
}), 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/<submission_id>", 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
6 changes: 5 additions & 1 deletion api/volunteers/volunteers_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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']:
Expand Down
34 changes: 33 additions & 1 deletion services/volunteers_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down