from flask import Blueprint, request, jsonify from tinydb import Query from db.db import child_db, task_db, reward_db from api.reward_status import RewardStatus from api.child_tasks import ChildTask from api.child_rewards import ChildReward from events.sse import send_to_user, send_event_to_user from events.types.child_add import ChildAdd from events.types.child_delete import ChildDelete from events.types.child_update import ChildUpdate from events.types.event import Event from events.types.event_types import EventType from events.types.reward_set import RewardSet from events.types.reward_update import RewardUpdate from events.types.task_set import TaskSet from events.types.task_update import TaskUpdate from models.child import Child from models.task import Task from models.reward import Reward child_api = Blueprint('child_api', __name__) @child_api.route('/child/', methods=['GET']) @child_api.route('/child/', methods=['GET']) def get_child(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 return jsonify(result[0]), 200 @child_api.route('/child/add', methods=['PUT']) def add_child(): data = request.get_json() name = data.get('name') age = data.get('age') image = data.get('image_id', None) if not name: return jsonify({'error': 'Name is required'}), 400 if not image: image = 'boy01' child = Child(name=name, age=age, image_id=image) child_db.insert(child.to_dict()) send_event_to_user("user123", Event(EventType.CHILD_ADD.value, ChildAdd(child.id, "set"))) return jsonify({'message': f'Child {name} added.'}), 201 @child_api.route('/child//edit', methods=['PUT']) def edit_child(id): data = request.get_json() name = data.get('name', None) age = data.get('age', None) points = data.get('points', None) image = data.get('image_id', None) ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] if name is not None: child['name'] = name if age is not None: child['age'] = age if points is not None: child['points'] = points if image is not None: child['image_id'] = image child_db.update(child, ChildQuery.id == id) send_event_to_user("user123", Event(EventType.CHILD_UPDATE.value, ChildUpdate(id, "set"))) return jsonify({'message': f'Child {id} updated.'}), 200 @child_api.route('/child/list', methods=['GET']) def list_children(): children = child_db.all() return jsonify({'children': children}), 200 # Child DELETE @child_api.route('/child/', methods=['DELETE']) def delete_child(id): ChildQuery = Query() if child_db.remove(ChildQuery.id == id): send_event_to_user("user123", Event(EventType.CHILD_DELETE.value, ChildDelete(id, "deleted"))) return jsonify({'message': f'Child {id} deleted.'}), 200 return jsonify({'error': 'Child not found'}), 404 @child_api.route('/child//assign-task', methods=['POST']) def assign_task_to_child(id): data = request.get_json() task_id = data.get('task_id') if not task_id: return jsonify({'error': 'task_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] if task_id not in child.get('tasks', []): child['tasks'].append(task_id) child_db.update({'tasks': child['tasks']}, ChildQuery.id == id) return jsonify({'message': f'Task {task_id} assigned to {child['name']}.'}), 200 # python @child_api.route('/child//set-tasks', methods=['PUT']) def set_child_tasks(id): data = request.get_json() or {} task_ids = data.get('task_ids') if not isinstance(task_ids, list): return jsonify({'error': 'task_ids must be a list'}), 400 # Deduplicate and drop falsy values new_task_ids = [tid for tid in dict.fromkeys(task_ids) if tid] ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 # Optional: validate task IDs exist in the task DB TaskQuery = Query() valid_task_ids = [] for tid in new_task_ids: if task_db.get(TaskQuery.id == tid): valid_task_ids.append(tid) # Replace tasks with validated IDs child_db.update({'tasks': valid_task_ids}, ChildQuery.id == id) send_event_to_user("user123", Event(EventType.TASK_SET.value, TaskSet(id, "set"))) return jsonify({ 'message': f'Tasks set for child {id}.', 'task_ids': valid_task_ids, 'count': len(valid_task_ids) }), 200 @child_api.route('/child//remove-task', methods=['POST']) def remove_task_from_child(id): data = request.get_json() task_id = data.get('task_id') if not task_id: return jsonify({'error': 'task_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] if task_id in child.get('tasks', []): child['tasks'].remove(task_id) child_db.update({'tasks': child['tasks']}, ChildQuery.id == id) return jsonify({'message': f'Task {task_id} removed from {child["name"]}.'}), 200 return jsonify({'error': 'Task not assigned to child'}), 400 @child_api.route('/child//list-tasks', methods=['GET']) def list_child_tasks(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] task_ids = child.get('tasks', []) TaskQuery = Query() child_tasks = [] for tid in task_ids: task = task_db.get(TaskQuery.id == tid) if not task: continue ct = ChildTask(task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id')) child_tasks.append(ct.to_dict()) return jsonify({'tasks': child_tasks}), 200 @child_api.route('/child//list-assignable-tasks', methods=['GET']) def list_assignable_tasks(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] assigned_ids = set(child.get('tasks', [])) # Collect all task ids from the task database all_task_ids = [t.get('id') for t in task_db.all() if t and t.get('id')] # Filter out already assigned assignable_ids = [tid for tid in all_task_ids if tid not in assigned_ids] # Fetch full task details and wrap in ChildTask TaskQuery = Query() assignable_tasks = [] for tid in assignable_ids: task = task_db.get(TaskQuery.id == tid) if not task: continue ct = ChildTask(task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id')) assignable_tasks.append(ct.to_dict()) return jsonify({'tasks': assignable_tasks, 'count': len(assignable_tasks)}), 200 @child_api.route('/child//list-all-tasks', methods=['GET']) def list_all_tasks(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] assigned_ids = set(child.get('tasks', [])) # Get all tasks from database all_tasks = task_db.all() assigned_tasks = [] assignable_tasks = [] for task in all_tasks: if not task or not task.get('id'): continue ct = ChildTask( task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id') ) if task.get('id') in assigned_ids: assigned_tasks.append(ct.to_dict()) else: assignable_tasks.append(ct.to_dict()) return jsonify({ 'assigned_tasks': assigned_tasks, 'assignable_tasks': assignable_tasks, 'assigned_count': len(assigned_tasks), 'assignable_count': len(assignable_tasks) }), 200 @child_api.route('/child//trigger-task', methods=['POST']) def trigger_child_task(id): data = request.get_json() task_id = data.get('task_id') if not task_id: return jsonify({'error': 'task_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child: Child = Child.from_dict(result[0]) if task_id not in child.tasks: return jsonify({'error': f'Task not found assigned to child {child.name}'}), 404 # look up the task and get the details TaskQuery = Query() task_result = task_db.search(TaskQuery.id == task_id) if not task_result: return jsonify({'error': 'Task not found in task database'}), 404 task: Task = Task.from_dict(task_result[0]) # update the child's points based on task type if task.is_good: child.points += task.points else: child.points -= task.points child.points = max(child.points, 0) # update the child in the database child_db.update({'points': child.points}, ChildQuery.id == id) send_event_to_user("user123", Event(EventType.TASK_UPDATE.value, TaskUpdate(task.id, child.id,"complete", child.points))) return jsonify({'message': f'{task.name} points assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200 @child_api.route('/child//assign-reward', methods=['POST']) def assign_reward_to_child(id): data = request.get_json() reward_id = data.get('reward_id') if not reward_id: return jsonify({'error': 'reward_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] if reward_id not in child.get('rewards', []): child['rewards'].append(reward_id) child_db.update({'rewards': child['rewards']}, ChildQuery.id == id) return jsonify({'message': f'Reward {reward_id} assigned to {child["name"]}.'}), 200 @child_api.route('/child//list-all-rewards', methods=['GET']) def list_all_rewards(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] assigned_ids = set(child.get('rewards', [])) # Get all rewards from database all_rewards = reward_db.all() assigned_rewards = [] assignable_rewards = [] for reward in all_rewards: if not reward or not reward.get('id'): continue cr = ChildReward( reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id') ) if reward.get('id') in assigned_ids: assigned_rewards.append(cr.to_dict()) else: assignable_rewards.append(cr.to_dict()) return jsonify({ 'assigned_rewards': assigned_rewards, 'assignable_rewards': assignable_rewards, 'assigned_count': len(assigned_rewards), 'assignable_count': len(assignable_rewards) }), 200 @child_api.route('/child//set-rewards', methods=['PUT']) def set_child_rewards(id): data = request.get_json() or {} reward_ids = data.get('reward_ids') if not isinstance(reward_ids, list): return jsonify({'error': 'reward_ids must be a list'}), 400 # Deduplicate and drop falsy values new_reward_ids = [rid for rid in dict.fromkeys(reward_ids) if rid] ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 # Optional: validate reward IDs exist in the reward DB RewardQuery = Query() valid_reward_ids = [] for rid in new_reward_ids: if reward_db.get(RewardQuery.id == rid): valid_reward_ids.append(rid) # Replace rewards with validated IDs child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id) send_event_to_user("user123", Event(EventType.REWARD_SET.value, RewardSet(id, "set"))) return jsonify({ 'message': f'Rewards set for child {id}.', 'reward_ids': valid_reward_ids, 'count': len(valid_reward_ids) }), 200 @child_api.route('/child//remove-reward', methods=['POST']) def remove_reward_from_child(id): data = request.get_json() reward_id = data.get('reward_id') if not reward_id: return jsonify({'error': 'reward_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] if reward_id in child.get('rewards', []): child['rewards'].remove(reward_id) child_db.update({'rewards': child['rewards']}, ChildQuery.id == id) return jsonify({'message': f'Reward {reward_id} removed from {child["name"]}.'}), 200 return jsonify({'error': 'Reward not assigned to child'}), 400 @child_api.route('/child//list-rewards', methods=['GET']) def list_child_rewards(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] reward_ids = child.get('rewards', []) RewardQuery = Query() child_rewards = [] for rid in reward_ids: reward = reward_db.get(RewardQuery.id == rid) if not reward: continue cr = ChildReward(reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id')) child_rewards.append(cr.to_dict()) return jsonify({'rewards': child_rewards}), 200 @child_api.route('/child//list-assignable-rewards', methods=['GET']) def list_assignable_rewards(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] assigned_ids = set(child.get('rewards', [])) all_reward_ids = [r.get('id') for r in reward_db.all() if r and r.get('id')] assignable_ids = [rid for rid in all_reward_ids if rid not in assigned_ids] RewardQuery = Query() assignable_rewards = [] for rid in assignable_ids: reward = reward_db.get(RewardQuery.id == rid) if not reward: continue cr = ChildReward(reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id')) assignable_rewards.append(cr.to_dict()) return jsonify({'rewards': assignable_rewards, 'count': len(assignable_rewards)}), 200 @child_api.route('/child//trigger-reward', methods=['POST']) def trigger_child_reward(id): data = request.get_json() reward_id = data.get('reward_id') if not reward_id: return jsonify({'error': 'reward_id is required'}), 400 ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child: Child = Child.from_dict(result[0]) if reward_id not in child.rewards: return jsonify({'error': f'Reward not found assigned to child {child.name}'}), 404 # look up the task and get the details RewardQuery = Query() reward_result = reward_db.search(RewardQuery.id == reward_id) if not reward_result: return jsonify({'error': 'Reward not found in reward database'}), 404 reward: Reward = Reward.from_dict(reward_result[0]) # update the child's points based on reward cost child.points -= reward.cost # update the child in the database child_db.update({'points': child.points}, ChildQuery.id == id) send_event_to_user("user123", Event(EventType.REWARD_UPDATE.value, RewardUpdate(reward.id, child.id, "redeemed", child.points))) return jsonify({'message': f'{reward.name} assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200 @child_api.route('/child//affordable-rewards', methods=['GET']) def list_affordable_rewards(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] points = child.get('points', 0) reward_ids = child.get('rewards', []) RewardQuery = Query() affordable = [ reward for reward_id in reward_ids if (reward := reward_db.get(RewardQuery.id == reward_id)) and points >= reward.get('cost', 0) ] return jsonify({'affordable_rewards': affordable}), 200 @child_api.route('/child//reward-status', methods=['GET']) def reward_status(id): ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 child = result[0] points = child.get('points', 0) reward_ids = child.get('rewards', []) RewardQuery = Query() statuses = [] for reward_id in reward_ids: reward: Reward = Reward.from_dict(reward_db.get(RewardQuery.id == reward_id)) if not reward: continue points_needed = max(0, reward.cost - points) status = RewardStatus(reward.id, reward.name, points_needed, reward.cost, reward.image_id) statuses.append(status.to_dict()) statuses.sort(key=lambda s: s['points_needed']) return jsonify({'reward_status': statuses}), 200