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
8 changes: 8 additions & 0 deletions api/hearts/hearts_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
bp = Blueprint(bp_name, __name__, url_prefix=bp_url_prefix)


@bp.route("/hearts/leaderboard", methods=["GET"])
def get_hearts_leaderboard():
limit = request.args.get("limit", 10, type=int)
limit = min(max(limit, 1), 50)
res = hearts_service.get_hearts_leaderboard(limit)
return {"leaderboard": res}


# Used to provide profile details - user must be logged in
@bp.route("/hearts", methods=["GET"])
@auth.require_user
Expand Down
1 change: 1 addition & 0 deletions api/messages/messages_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,7 @@ def save_hackathon(json_data, propel_id):
"prize": "0",
"swag": "0",
}),
"timezone": json_data.get("timezone", "America/Phoenix"),
"last_updated": firestore.SERVER_TIMESTAMP,
"last_updated_by": propel_id,
}
Expand Down
36 changes: 35 additions & 1 deletion api/volunteers/volunteers_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
mentor_checkout,
send_volunteer_message,
send_email_to_address,
get_resend_email_statuses,
)
from common.auth import auth, auth_user

Expand Down Expand Up @@ -593,6 +594,7 @@ def admin_send_email_to_address():
subject = request_data.get('subject', 'Message from Opportunity Hack Team')
recipient_type = request_data.get('recipient_type', 'volunteer')
name = request_data.get('name')
volunteer_id = request_data.get('volunteer_id')

if not email:
return _error_response("Email address is required", 400)
Expand All @@ -609,7 +611,8 @@ def admin_send_email_to_address():
admin_user_id=auth_user.user_id,
admin_user=auth_user,
recipient_type=recipient_type,
name=name
name=name,
volunteer_id=volunteer_id
)

if result['success']:
Expand All @@ -622,3 +625,34 @@ def admin_send_email_to_address():
except Exception as e:
logger.error("Error in admin_send_email_to_address: %s", str(e))
return _error_response(f"Failed to send email: {str(e)}")


@bp.route('/admin/emails/resend-status', methods=['POST'])
@auth.require_org_member_with_permission("volunteer.admin", req_to_org_id=getOrgId)
def admin_get_resend_email_statuses():
"""Admin endpoint to fetch delivery status for Resend email IDs."""
try:
request_data = _process_request()
email_ids = request_data.get('email_ids', [])

logger.info("Fetching Resend email statuses for %d email IDs", len(email_ids) if isinstance(email_ids, list) else 0)
logger.debug("Email IDs requested: %s", email_ids)

if not email_ids or not isinstance(email_ids, list):
logger.warning("Invalid or missing email_ids in request: %s", email_ids)
return _error_response("email_ids array is required", 400)

result = get_resend_email_statuses(email_ids)

if result['success']:
logger.info("Successfully fetched statuses for %d email IDs", len(email_ids))
logger.debug("Email status results: %s", result)
return _success_response(result, "Email statuses fetched successfully")

logger.error("Failed to fetch email statuses. Error: %s", result.get('error', 'Unknown error'))
return _error_response(result.get('error', 'Unknown error'), 500)

except Exception as e:
logger.error("Error in admin_get_resend_email_statuses: %s", str(e))
logger.exception(e)
return _error_response(f"Failed to fetch email statuses: {str(e)}")
25 changes: 24 additions & 1 deletion common/utils/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from urllib.parse import urlparse
import logging
from datetime import datetime
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -96,12 +97,34 @@ def validate_hackathon_data(data):
except ValueError as e:
raise ValueError(f"Invalid date format: {str(e)}")

# Validate timezone if provided
timezone = data.get("timezone")
if timezone:
try:
ZoneInfo(timezone)
except (ZoneInfoNotFoundError, KeyError):
raise ValueError(f"Invalid timezone: {timezone}")

# Validate constraints
constraints = data.get("constraints", {})
if not all(isinstance(constraints.get(k), int) for k in ["max_people_per_team", "max_teams_per_problem", "min_people_per_team"]):
raise ValueError("Constraints must be integers")

# Add more specific validations as needed
# Validate hacker_required_questions if present
hacker_required_questions = constraints.get("hacker_required_questions", {})
if hacker_required_questions:
questions = hacker_required_questions.get("questions", [])
if not isinstance(questions, list):
raise ValueError("hacker_required_questions.questions must be a list")
for i, q in enumerate(questions):
if not isinstance(q, dict):
raise ValueError(f"Question {i} must be an object")
if not isinstance(q.get("question"), str) or not q.get("question"):
raise ValueError(f"Question {i} must have a non-empty 'question' string")
if not isinstance(q.get("required_answer"), bool):
raise ValueError(f"Question {i} must have a boolean 'required_answer'")
if not isinstance(q.get("error"), str) or not q.get("error"):
raise ValueError(f"Question {i} must have a non-empty 'error' string")

if __name__ == "__main__":
# Simple tests
Expand Down
25 changes: 25 additions & 0 deletions services/hearts_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,31 @@ def get_hearts_for_all_users():
return result


def get_hearts_leaderboard(limit: int = 10) -> list:
users = fetch_users()
result = []

for user in users:
total_hearts = 0

if user.history:
for key in user.history:
if "certificates" in key:
continue
for subkey in user.history[key]:
total_hearts += user.history[key][subkey]

if total_hearts > 0:
result.append({
"name": user.name,
"totalHearts": total_hearts,
"userId": user.id,
"profileImage": user.profile_image,
})

result.sort(key=lambda x: x["totalHearts"], reverse=True)
return result[:limit]



def save_hearts(user_id, hearts_json):
Expand Down
Loading