Files
chore/backend/db/db.py
Ryan Kegel eb775ba7d8
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s
feat: Implement routines feature with CRUD operations and child assignment
- 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.
2026-05-05 09:08:19 -04:00

149 lines
5.7 KiB
Python

# python
import os
from config.paths import get_database_dir
import threading
from tinydb import TinyDB
base_dir = get_database_dir()
os.makedirs(base_dir, exist_ok=True)
class LockedTable:
"""
Thread-safe wrapper around a TinyDB table. All callable attribute access
is wrapped to acquire a reentrant lock while calling the underlying method.
Non-callable attributes are returned directly.
"""
def __init__(self, table):
self._table = table
self._lock = threading.RLock()
def __getattr__(self, name):
# avoid proxying internal attrs
if name in ('_table', '_lock'):
return super().__getattribute__(name)
attr = getattr(self._table, name)
if callable(attr):
def locked_call(*args, **kwargs):
with self._lock:
return attr(*args, **kwargs)
return locked_call
return attr
# convenience explicit methods (ensure these are class methods, not top-level)
def insert(self, *args, **kwargs):
with self._lock:
return self._table.insert(*args, **kwargs)
def insert_multiple(self, *args, **kwargs):
with self._lock:
return self._table.insert_multiple(*args, **kwargs)
def search(self, *args, **kwargs):
with self._lock:
return self._table.search(*args, **kwargs)
def get(self, *args, **kwargs):
with self._lock:
return self._table.get(*args, **kwargs)
def all(self, *args, **kwargs):
with self._lock:
return self._table.all(*args, **kwargs)
def remove(self, *args, **kwargs):
with self._lock:
return self._table.remove(*args, **kwargs)
def update(self, *args, **kwargs):
with self._lock:
return self._table.update(*args, **kwargs)
def truncate(self):
with self._lock:
return self._table.truncate()
# Setup DB files next to this module
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')
pending_confirmations_path = os.path.join(base_dir, 'pending_confirmations.json')
users_path = os.path.join(base_dir, 'users.json')
tracking_events_path = os.path.join(base_dir, 'tracking_events.json')
child_overrides_path = os.path.join(base_dir, 'child_overrides.json')
chore_schedules_path = os.path.join(base_dir, 'chore_schedules.json')
task_extensions_path = os.path.join(base_dir, 'task_extensions.json')
refresh_tokens_path = os.path.join(base_dir, 'refresh_tokens.json')
push_subscriptions_path = os.path.join(base_dir, 'push_subscriptions.json')
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)
_pending_confirmations_db = TinyDB(pending_confirmations_path, indent=2)
_users_db = TinyDB(users_path, indent=2)
_tracking_events_db = TinyDB(tracking_events_path, indent=2)
_child_overrides_db = TinyDB(child_overrides_path, indent=2)
_chore_schedules_db = TinyDB(chore_schedules_path, indent=2)
_task_extensions_db = TinyDB(task_extensions_path, indent=2)
_refresh_tokens_db = TinyDB(refresh_tokens_path, indent=2)
_push_subscriptions_db = TinyDB(push_subscriptions_path, indent=2)
_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)
pending_confirmations_db = LockedTable(_pending_confirmations_db)
users_db = LockedTable(_users_db)
tracking_events_db = LockedTable(_tracking_events_db)
child_overrides_db = LockedTable(_child_overrides_db)
chore_schedules_db = LockedTable(_chore_schedules_db)
task_extensions_db = LockedTable(_task_extensions_db)
refresh_tokens_db = LockedTable(_refresh_tokens_db)
push_subscriptions_db = LockedTable(_push_subscriptions_db)
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()
pending_confirmations_db.truncate()
users_db.truncate()
tracking_events_db.truncate()
child_overrides_db.truncate()
chore_schedules_db.truncate()
task_extensions_db.truncate()
refresh_tokens_db.truncate()
push_subscriptions_db.truncate()
digest_action_tokens_db.truncate()