from flask import Blueprint, request, jsonify from tinydb import Query from api.utils import get_validated_user_id, send_event_for_current_user from api.error_codes import ErrorCodes from db.db import child_db, task_db, reward_db from db.child_overrides import ( insert_override, get_override, get_overrides_for_child, delete_override ) from models.child_override import ChildOverride from events.types.event import Event from events.types.event_types import EventType from events.types.child_override_set import ChildOverrideSetPayload from events.types.child_override_deleted import ChildOverrideDeletedPayload import logging child_override_api = Blueprint('child_override_api', __name__) logger = logging.getLogger(__name__) @child_override_api.route('/child//override', methods=['PUT']) def set_child_override(child_id): """ Set or update a custom value for a task/reward for a specific child. """ user_id = get_validated_user_id() if not user_id: return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401 # Validate child exists and belongs to user ChildQuery = Query() child_result = child_db.search((ChildQuery.id == child_id) & (ChildQuery.user_id == user_id)) if not child_result: return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404 child_dict = child_result[0] # Parse request data data = request.get_json() or {} entity_id = data.get('entity_id') entity_type = data.get('entity_type') custom_value = data.get('custom_value') # Validate required fields if not entity_id: return jsonify({'error': 'entity_id is required', 'code': ErrorCodes.MISSING_FIELD, 'field': 'entity_id'}), 400 if not entity_type: return jsonify({'error': 'entity_type is required', 'code': ErrorCodes.MISSING_FIELD, 'field': 'entity_type'}), 400 if custom_value is None: return jsonify({'error': 'custom_value is required', 'code': ErrorCodes.MISSING_FIELD, 'field': 'custom_value'}), 400 # Validate entity_type if entity_type not in ['task', 'reward']: return jsonify({'error': 'entity_type must be "task" or "reward"', 'code': ErrorCodes.INVALID_VALUE, 'field': 'entity_type'}), 400 # Validate custom_value range if not isinstance(custom_value, int) or custom_value < 0 or custom_value > 10000: return jsonify({'error': 'custom_value must be an integer between 0 and 10000', 'code': ErrorCodes.INVALID_VALUE, 'field': 'custom_value'}), 400 # Validate entity exists and is assigned to child if entity_type == 'task': EntityQuery = Query() entity_result = task_db.search( (EntityQuery.id == entity_id) & ((EntityQuery.user_id == user_id) | (EntityQuery.user_id == None)) ) if not entity_result: return jsonify({'error': 'Task not found', 'code': ErrorCodes.TASK_NOT_FOUND}), 404 # Check if task is assigned to child assigned_tasks = child_dict.get('tasks', []) if entity_id not in assigned_tasks: return jsonify({'error': 'Task not assigned to child', 'code': ErrorCodes.ENTITY_NOT_ASSIGNED}), 404 else: # reward EntityQuery = Query() entity_result = reward_db.search( (EntityQuery.id == entity_id) & ((EntityQuery.user_id == user_id) | (EntityQuery.user_id == None)) ) if not entity_result: return jsonify({'error': 'Reward not found', 'code': ErrorCodes.REWARD_NOT_FOUND}), 404 # Check if reward is assigned to child assigned_rewards = child_dict.get('rewards', []) if entity_id not in assigned_rewards: return jsonify({'error': 'Reward not assigned to child', 'code': ErrorCodes.ENTITY_NOT_ASSIGNED}), 404 # Create and insert override try: override = ChildOverride.create_override( child_id=child_id, entity_id=entity_id, entity_type=entity_type, custom_value=custom_value ) insert_override(override) # Send SSE event resp = send_event_for_current_user( Event(EventType.CHILD_OVERRIDE_SET.value, ChildOverrideSetPayload(override)) ) if resp: return resp return jsonify({'override': override.to_dict()}), 200 except ValueError as e: return jsonify({'error': str(e), 'code': ErrorCodes.VALIDATION_ERROR}), 400 except Exception as e: logger.error(f"Error setting override: {e}") return jsonify({'error': 'Internal server error', 'code': ErrorCodes.INTERNAL_ERROR}), 500 @child_override_api.route('/child//overrides', methods=['GET']) def get_child_overrides(child_id): """ Get all overrides for a specific child. """ user_id = get_validated_user_id() if not user_id: return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401 # Validate child exists and belongs to user ChildQuery = Query() child_result = child_db.search((ChildQuery.id == child_id) & (ChildQuery.user_id == user_id)) if not child_result: return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404 # Get all overrides for child overrides = get_overrides_for_child(child_id) return jsonify({'overrides': [o.to_dict() for o in overrides]}), 200 @child_override_api.route('/child//override/', methods=['DELETE']) def delete_child_override(child_id, entity_id): """ Delete an override (reset to default). """ user_id = get_validated_user_id() if not user_id: return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401 # Validate child exists and belongs to user ChildQuery = Query() child_result = child_db.search((ChildQuery.id == child_id) & (ChildQuery.user_id == user_id)) if not child_result: return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404 # Get override to determine entity_type for event override = get_override(child_id, entity_id) if not override: return jsonify({'error': 'Override not found', 'code': ErrorCodes.OVERRIDE_NOT_FOUND}), 404 entity_type = override.entity_type # Delete override deleted = delete_override(child_id, entity_id) if not deleted: return jsonify({'error': 'Override not found', 'code': ErrorCodes.OVERRIDE_NOT_FOUND}), 404 # Send SSE event resp = send_event_for_current_user( Event(EventType.CHILD_OVERRIDE_DELETED.value, ChildOverrideDeletedPayload(child_id, entity_id, entity_type)) ) if resp: return resp return jsonify({'message': 'Override deleted'}), 200