feat: Implement routines feature with CRUD operations and child assignment
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s
- Add backend routines management with add, get, update, delete, and list functionalities. - Create models for Routine, RoutineItem, RoutineSchedule, and RoutineExtension. - Develop event types for routine confirmation and modification. - Implement frontend components for routine assignment, confirmation dialog, and routine management views. - Add unit tests for routine API and integration tests for routine CRUD flow. - Create end-to-end test plan for routines feature covering parent and child interactions.
This commit is contained in:
@@ -69,6 +69,10 @@ class LockedTable:
|
||||
|
||||
child_path = os.path.join(base_dir, 'children.json')
|
||||
task_path = os.path.join(base_dir, 'tasks.json')
|
||||
routine_path = os.path.join(base_dir, 'routines.json')
|
||||
routine_items_path = os.path.join(base_dir, 'routine_items.json')
|
||||
routine_schedules_path = os.path.join(base_dir, 'routine_schedules.json')
|
||||
routine_extensions_path = os.path.join(base_dir, 'routine_extensions.json')
|
||||
reward_path = os.path.join(base_dir, 'rewards.json')
|
||||
image_path = os.path.join(base_dir, 'images.json')
|
||||
pending_reward_path = os.path.join(base_dir, 'pending_rewards.json')
|
||||
@@ -85,6 +89,10 @@ digest_action_tokens_path = os.path.join(base_dir, 'digest_action_tokens.json')
|
||||
# Use separate TinyDB instances/files for each collection
|
||||
_child_db = TinyDB(child_path, indent=2)
|
||||
_task_db = TinyDB(task_path, indent=2)
|
||||
_routine_db = TinyDB(routine_path, indent=2)
|
||||
_routine_items_db = TinyDB(routine_items_path, indent=2)
|
||||
_routine_schedules_db = TinyDB(routine_schedules_path, indent=2)
|
||||
_routine_extensions_db = TinyDB(routine_extensions_path, indent=2)
|
||||
_reward_db = TinyDB(reward_path, indent=2)
|
||||
_image_db = TinyDB(image_path, indent=2)
|
||||
_pending_rewards_db = TinyDB(pending_reward_path, indent=2)
|
||||
@@ -101,6 +109,10 @@ _digest_action_tokens_db = TinyDB(digest_action_tokens_path, indent=2)
|
||||
# Expose table objects wrapped with locking
|
||||
child_db = LockedTable(_child_db)
|
||||
task_db = LockedTable(_task_db)
|
||||
routine_db = LockedTable(_routine_db)
|
||||
routine_items_db = LockedTable(_routine_items_db)
|
||||
routine_schedules_db = LockedTable(_routine_schedules_db)
|
||||
routine_extensions_db = LockedTable(_routine_extensions_db)
|
||||
reward_db = LockedTable(_reward_db)
|
||||
image_db = LockedTable(_image_db)
|
||||
pending_reward_db = LockedTable(_pending_rewards_db)
|
||||
@@ -117,6 +129,10 @@ digest_action_tokens_db = LockedTable(_digest_action_tokens_db)
|
||||
if os.environ.get('DB_ENV', 'prod') == 'test':
|
||||
child_db.truncate()
|
||||
task_db.truncate()
|
||||
routine_db.truncate()
|
||||
routine_items_db.truncate()
|
||||
routine_schedules_db.truncate()
|
||||
routine_extensions_db.truncate()
|
||||
reward_db.truncate()
|
||||
image_db.truncate()
|
||||
pending_reward_db.truncate()
|
||||
|
||||
41
backend/db/routine_extensions.py
Normal file
41
backend/db/routine_extensions.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from tinydb import Query
|
||||
from db.db import routine_extensions_db
|
||||
from models.routine_extension import RoutineExtension
|
||||
|
||||
|
||||
def get_extension(child_id: str, routine_id: str, date: str) -> RoutineExtension | None:
|
||||
q = Query()
|
||||
result = routine_extensions_db.search(
|
||||
(q.child_id == child_id) & (q.routine_id == routine_id) & (q.date == date)
|
||||
)
|
||||
if not result:
|
||||
return None
|
||||
return RoutineExtension.from_dict(result[0])
|
||||
|
||||
|
||||
def add_extension(extension: RoutineExtension) -> None:
|
||||
routine_extensions_db.insert(extension.to_dict())
|
||||
|
||||
|
||||
def delete_extensions_for_child(child_id: str) -> None:
|
||||
q = Query()
|
||||
routine_extensions_db.remove(q.child_id == child_id)
|
||||
|
||||
|
||||
def delete_extensions_for_routine(routine_id: str) -> None:
|
||||
q = Query()
|
||||
routine_extensions_db.remove(q.routine_id == routine_id)
|
||||
|
||||
|
||||
def delete_extension_for_child_routine(child_id: str, routine_id: str) -> None:
|
||||
q = Query()
|
||||
routine_extensions_db.remove((q.child_id == child_id) & (q.routine_id == routine_id))
|
||||
|
||||
|
||||
def get_extension_for_child_routine(child_id: str, routine_id: str) -> RoutineExtension | None:
|
||||
q = Query()
|
||||
results = routine_extensions_db.search((q.child_id == child_id) & (q.routine_id == routine_id))
|
||||
if not results:
|
||||
return None
|
||||
latest = max(results, key=lambda r: r.get('date', ''))
|
||||
return RoutineExtension.from_dict(latest)
|
||||
45
backend/db/routine_items.py
Normal file
45
backend/db/routine_items.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from tinydb import Query
|
||||
from db.db import routine_items_db
|
||||
from models.routine_item import RoutineItem
|
||||
|
||||
|
||||
def add_item(item: RoutineItem) -> None:
|
||||
routine_items_db.insert(item.to_dict())
|
||||
|
||||
|
||||
def get_item(item_id: str) -> RoutineItem | None:
|
||||
q = Query()
|
||||
result = routine_items_db.search(q.id == item_id)
|
||||
if not result:
|
||||
return None
|
||||
return RoutineItem.from_dict(result[0])
|
||||
|
||||
|
||||
def get_items_for_routine(routine_id: str) -> list[RoutineItem]:
|
||||
q = Query()
|
||||
results = routine_items_db.search(q.routine_id == routine_id)
|
||||
items = [RoutineItem.from_dict(r) for r in results]
|
||||
return sorted(items, key=lambda i: (i.order, i.created_at))
|
||||
|
||||
|
||||
def update_item(item: RoutineItem) -> bool:
|
||||
q = Query()
|
||||
existing = routine_items_db.get(q.id == item.id)
|
||||
if not existing:
|
||||
return False
|
||||
routine_items_db.update(item.to_dict(), q.id == item.id)
|
||||
return True
|
||||
|
||||
|
||||
def delete_item(item_id: str) -> bool:
|
||||
q = Query()
|
||||
existing = routine_items_db.get(q.id == item_id)
|
||||
if not existing:
|
||||
return False
|
||||
routine_items_db.remove(q.id == item_id)
|
||||
return True
|
||||
|
||||
|
||||
def delete_for_routine(routine_id: str) -> None:
|
||||
q = Query()
|
||||
routine_items_db.remove(q.routine_id == routine_id)
|
||||
42
backend/db/routine_schedules.py
Normal file
42
backend/db/routine_schedules.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from tinydb import Query
|
||||
from db.db import routine_schedules_db
|
||||
from models.routine_schedule import RoutineSchedule
|
||||
|
||||
|
||||
def get_schedule(child_id: str, routine_id: str) -> RoutineSchedule | None:
|
||||
q = Query()
|
||||
result = routine_schedules_db.search((q.child_id == child_id) & (q.routine_id == routine_id))
|
||||
if not result:
|
||||
return None
|
||||
return RoutineSchedule.from_dict(result[0])
|
||||
|
||||
|
||||
def upsert_schedule(schedule: RoutineSchedule) -> None:
|
||||
q = Query()
|
||||
existing = routine_schedules_db.get((q.child_id == schedule.child_id) & (q.routine_id == schedule.routine_id))
|
||||
if existing:
|
||||
routine_schedules_db.update(
|
||||
schedule.to_dict(),
|
||||
(q.child_id == schedule.child_id) & (q.routine_id == schedule.routine_id)
|
||||
)
|
||||
else:
|
||||
routine_schedules_db.insert(schedule.to_dict())
|
||||
|
||||
|
||||
def delete_schedule(child_id: str, routine_id: str) -> bool:
|
||||
q = Query()
|
||||
existing = routine_schedules_db.get((q.child_id == child_id) & (q.routine_id == routine_id))
|
||||
if not existing:
|
||||
return False
|
||||
routine_schedules_db.remove((q.child_id == child_id) & (q.routine_id == routine_id))
|
||||
return True
|
||||
|
||||
|
||||
def delete_schedules_for_child(child_id: str) -> None:
|
||||
q = Query()
|
||||
routine_schedules_db.remove(q.child_id == child_id)
|
||||
|
||||
|
||||
def delete_schedules_for_routine(routine_id: str) -> None:
|
||||
q = Query()
|
||||
routine_schedules_db.remove(q.routine_id == routine_id)
|
||||
39
backend/db/routines.py
Normal file
39
backend/db/routines.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from tinydb import Query
|
||||
from db.db import routine_db
|
||||
from models.routine import Routine
|
||||
|
||||
|
||||
def add_routine(routine: Routine) -> None:
|
||||
routine_db.insert(routine.to_dict())
|
||||
|
||||
|
||||
def get_routine(routine_id: str) -> Routine | None:
|
||||
q = Query()
|
||||
result = routine_db.search(q.id == routine_id)
|
||||
if not result:
|
||||
return None
|
||||
return Routine.from_dict(result[0])
|
||||
|
||||
|
||||
def update_routine(routine: Routine) -> bool:
|
||||
q = Query()
|
||||
existing = routine_db.get(q.id == routine.id)
|
||||
if not existing:
|
||||
return False
|
||||
routine_db.update(routine.to_dict(), q.id == routine.id)
|
||||
return True
|
||||
|
||||
|
||||
def delete_routine(routine_id: str) -> bool:
|
||||
q = Query()
|
||||
existing = routine_db.get(q.id == routine_id)
|
||||
if not existing:
|
||||
return False
|
||||
routine_db.remove(q.id == routine_id)
|
||||
return True
|
||||
|
||||
|
||||
def list_routines_for_user(user_id: str) -> list[Routine]:
|
||||
q = Query()
|
||||
results = routine_db.search((q.user_id == user_id) | (q.user_id == None))
|
||||
return [Routine.from_dict(r) for r in results]
|
||||
Reference in New Issue
Block a user