All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s
- Implemented ChoreAssignView for assigning chores to children. - Created ChoreConfirmDialog for confirming chore completion. - Developed KindnessAssignView for assigning kindness acts. - Added PenaltyAssignView for assigning penalties. - Introduced ChoreEditView and ChoreView for editing and viewing chores. - Created KindnessEditView and KindnessView for managing kindness acts. - Developed PenaltyEditView and PenaltyView for managing penalties. - Added TaskSubNav for navigation between chores, kindness acts, and penalties.
166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
from flask import Blueprint, request, jsonify
|
|
from tinydb import Query
|
|
|
|
from api.utils import send_event_for_current_user, get_validated_user_id
|
|
from events.types.child_tasks_set import ChildTasksSet
|
|
from db.db import task_db, child_db
|
|
from db.child_overrides import delete_overrides_for_entity
|
|
from events.types.event import Event
|
|
from events.types.event_types import EventType
|
|
from events.types.task_modified import TaskModified
|
|
from models.task import Task
|
|
|
|
chore_api = Blueprint('chore_api', __name__)
|
|
|
|
TASK_TYPE = 'chore'
|
|
|
|
|
|
@chore_api.route('/chore/add', methods=['PUT'])
|
|
def add_chore():
|
|
user_id = get_validated_user_id()
|
|
if not user_id:
|
|
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
|
data = request.get_json()
|
|
name = data.get('name')
|
|
points = data.get('points')
|
|
image = data.get('image_id', '')
|
|
if not name or points is None:
|
|
return jsonify({'error': 'Name and points are required'}), 400
|
|
task = Task(name=name, points=points, type=TASK_TYPE, image_id=image, user_id=user_id)
|
|
task_db.insert(task.to_dict())
|
|
send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
|
TaskModified(task.id, TaskModified.OPERATION_ADD)))
|
|
return jsonify({'message': f'Chore {name} added.'}), 201
|
|
|
|
|
|
@chore_api.route('/chore/<id>', methods=['GET'])
|
|
def get_chore(id):
|
|
user_id = get_validated_user_id()
|
|
if not user_id:
|
|
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
|
TaskQuery = Query()
|
|
result = task_db.search(
|
|
(TaskQuery.id == id) &
|
|
((TaskQuery.user_id == user_id) | (TaskQuery.user_id == None)) &
|
|
(TaskQuery.type == TASK_TYPE)
|
|
)
|
|
if not result:
|
|
return jsonify({'error': 'Chore not found'}), 404
|
|
return jsonify(result[0]), 200
|
|
|
|
|
|
@chore_api.route('/chore/list', methods=['GET'])
|
|
def list_chores():
|
|
user_id = get_validated_user_id()
|
|
if not user_id:
|
|
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
|
TaskQuery = Query()
|
|
tasks = task_db.search(
|
|
((TaskQuery.user_id == user_id) | (TaskQuery.user_id == None)) &
|
|
(TaskQuery.type == TASK_TYPE)
|
|
)
|
|
|
|
user_tasks = {t['name'].strip().lower(): t for t in tasks if t.get('user_id') == user_id}
|
|
filtered_tasks = []
|
|
for t in tasks:
|
|
if t.get('user_id') is None and t['name'].strip().lower() in user_tasks:
|
|
continue
|
|
filtered_tasks.append(t)
|
|
|
|
def sort_user_then_default(tasks_group):
|
|
user_created = sorted(
|
|
[t for t in tasks_group if t.get('user_id') == user_id],
|
|
key=lambda x: x['name'].lower(),
|
|
)
|
|
default_items = sorted(
|
|
[t for t in tasks_group if t.get('user_id') is None],
|
|
key=lambda x: x['name'].lower(),
|
|
)
|
|
return user_created + default_items
|
|
|
|
sorted_tasks = sort_user_then_default(filtered_tasks)
|
|
return jsonify({'tasks': sorted_tasks}), 200
|
|
|
|
|
|
@chore_api.route('/chore/<id>', methods=['DELETE'])
|
|
def delete_chore(id):
|
|
user_id = get_validated_user_id()
|
|
if not user_id:
|
|
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
|
TaskQuery = Query()
|
|
task = task_db.get((TaskQuery.id == id) & (TaskQuery.type == TASK_TYPE))
|
|
if not task:
|
|
return jsonify({'error': 'Chore not found'}), 404
|
|
if task.get('user_id') is None:
|
|
import logging
|
|
logging.warning(f"Forbidden delete attempt on system chore: id={id}, by user_id={user_id}")
|
|
return jsonify({'error': 'System chores cannot be deleted.'}), 403
|
|
removed = task_db.remove((TaskQuery.id == id) & (TaskQuery.user_id == user_id))
|
|
if removed:
|
|
deleted_count = delete_overrides_for_entity(id)
|
|
if deleted_count > 0:
|
|
import logging
|
|
logging.info(f"Cascade deleted {deleted_count} overrides for chore {id}")
|
|
ChildQuery = Query()
|
|
for child in child_db.all():
|
|
child_tasks = child.get('tasks', [])
|
|
if id in child_tasks:
|
|
child_tasks.remove(id)
|
|
child_db.update({'tasks': child_tasks}, ChildQuery.id == child.get('id'))
|
|
send_event_for_current_user(Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, child_tasks)))
|
|
send_event_for_current_user(Event(EventType.TASK_MODIFIED.value, TaskModified(id, TaskModified.OPERATION_DELETE)))
|
|
return jsonify({'message': f'Chore {id} deleted.'}), 200
|
|
return jsonify({'error': 'Chore not found'}), 404
|
|
|
|
|
|
@chore_api.route('/chore/<id>/edit', methods=['PUT'])
|
|
def edit_chore(id):
|
|
user_id = get_validated_user_id()
|
|
if not user_id:
|
|
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
|
TaskQuery = Query()
|
|
existing = task_db.get(
|
|
(TaskQuery.id == id) &
|
|
((TaskQuery.user_id == user_id) | (TaskQuery.user_id == None)) &
|
|
(TaskQuery.type == TASK_TYPE)
|
|
)
|
|
if not existing:
|
|
return jsonify({'error': 'Chore not found'}), 404
|
|
|
|
task = Task.from_dict(existing)
|
|
is_dirty = False
|
|
data = request.get_json(force=True) or {}
|
|
|
|
if 'name' in data:
|
|
name = data.get('name', '').strip()
|
|
if not name:
|
|
return jsonify({'error': 'Name cannot be empty'}), 400
|
|
task.name = name
|
|
is_dirty = True
|
|
|
|
if 'points' in data:
|
|
points = data.get('points')
|
|
if not isinstance(points, int) or points <= 0:
|
|
return jsonify({'error': 'Points must be a positive integer'}), 400
|
|
task.points = points
|
|
is_dirty = True
|
|
|
|
if 'image_id' in data:
|
|
task.image_id = data.get('image_id', '')
|
|
is_dirty = True
|
|
|
|
if not is_dirty:
|
|
return jsonify({'error': 'No valid fields to update'}), 400
|
|
|
|
if task.user_id is None:
|
|
new_task = Task(name=task.name, points=task.points, type=TASK_TYPE, image_id=task.image_id, user_id=user_id)
|
|
task_db.insert(new_task.to_dict())
|
|
send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
|
TaskModified(new_task.id, TaskModified.OPERATION_ADD)))
|
|
return jsonify(new_task.to_dict()), 200
|
|
|
|
task_db.update(task.to_dict(), (TaskQuery.id == id) & ((TaskQuery.user_id == user_id) | (TaskQuery.user_id == None)))
|
|
send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
|
TaskModified(id, TaskModified.OPERATION_EDIT)))
|
|
return jsonify(task.to_dict()), 200
|