All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s
- Implemented PendingRewardDialog for handling pending reward requests. - Created RewardConfirmDialog for confirming reward redemption. - Developed TaskConfirmDialog for task confirmation with child name display. test: add unit tests for ChildView and ParentView components - Added comprehensive tests for ChildView including task triggering and SSE event handling. - Implemented tests for ParentView focusing on override modal and SSE event management. test: add ScrollingList component tests - Created tests for ScrollingList to verify item fetching, loading states, and custom item classes. - Included tests for two-step click interactions and edit button display logic. - Moved toward hashed passwords.
174 lines
6.8 KiB
Python
174 lines
6.8 KiB
Python
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/<child_id>/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/<child_id>/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/<child_id>/override/<entity_id>', 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
|