import logging from flask import Blueprint, redirect, make_response, jsonify, request from tinydb import Query from utils.digest_token import validate_and_consume_token, validate_unsubscribe_token, peek_token from db.db import users_db from api.child_action_helpers import approve_chore, reject_chore, approve_reward_request, deny_reward from api.utils import get_validated_user_id digest_action_api = Blueprint('digest_action_api', __name__) logger = logging.getLogger(__name__) _ERROR_HTML = """ Link Error

This link is invalid or has expired.

Action links expire after 24 hours and can only be used once.

""" _UNSUB_HTML = """ Unsubscribed

You have been unsubscribed from daily digest emails.

To re-enable, visit your profile in the app.

""" _UNSUB_ERROR_HTML = """ Link Error

This unsubscribe link is invalid or has expired.

""" @digest_action_api.route('/digest-action/', methods=['GET']) def handle_digest_action(token_id: str): """ Validate a digest action token (without consuming it) and redirect to the frontend ParentView with the token embedded so the action executes only after the user authenticates as a parent. """ from flask import current_app frontend_url = current_app.config.get('FRONTEND_URL', 'https://localhost:5173') token = peek_token(token_id) if not token: return make_response(_ERROR_HTML, 400) deep_link = ( f"{frontend_url}/parent/{token.child_id}" f"?digestToken={token_id}&scrollTo={token.entity_id}&entityType={token.entity_type}" ) return redirect(deep_link, 302) @digest_action_api.route('/digest-action/', methods=['POST']) def execute_digest_action(token_id: str): """ Execute a digest action. Requires the user to be authenticated (JWT cookie). Validates and consumes the token, then performs the approve/deny action. """ user_id = get_validated_user_id() if not user_id: return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401 token = validate_and_consume_token(token_id) if not token: return jsonify({'error': 'This link is invalid or has expired.', 'code': 'INVALID_TOKEN'}), 400 if token.user_id != user_id: return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 403 try: if token.entity_type == 'chore' and token.action == 'approve': approve_chore(user_id, token.child_id, token.entity_id) elif token.entity_type == 'chore' and token.action == 'deny': reject_chore(user_id, token.child_id, token.entity_id) elif token.entity_type == 'reward' and token.action == 'approve': approve_reward_request(user_id, token.child_id, token.entity_id) elif token.entity_type == 'reward' and token.action == 'deny': deny_reward(user_id, token.child_id, token.entity_id) else: return jsonify({'error': 'Unknown action', 'code': 'INVALID_ACTION'}), 400 except Exception as e: logger.error(f'Error executing digest action {token.action}/{token.entity_type}: {e}') return jsonify({'error': 'Failed to execute action', 'code': 'ACTION_FAILED'}), 400 return jsonify({ 'success': True, 'child_id': token.child_id, 'entity_id': token.entity_id, 'entity_type': token.entity_type, 'action': token.action, }), 200 @digest_action_api.route('/digest-unsubscribe/', methods=['GET']) def handle_digest_unsubscribe(token: str): user_id = validate_unsubscribe_token(token) if not user_id: return make_response(_UNSUB_ERROR_HTML, 400) UserQ = Query() users_db.update({'email_digest_enabled': False}, UserQ.id == user_id) logger.info(f'User {user_id} unsubscribed from digest via email link') return make_response(_UNSUB_HTML, 200)