This commit is contained in:
100
backend/db/db.py
Normal file
100
backend/db/db.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# 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')
|
||||
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')
|
||||
users_path = os.path.join(base_dir, 'users.json')
|
||||
|
||||
# Use separate TinyDB instances/files for each collection
|
||||
_child_db = TinyDB(child_path, indent=2)
|
||||
_task_db = TinyDB(task_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)
|
||||
_users_db = TinyDB(users_path, indent=2)
|
||||
|
||||
# Expose table objects wrapped with locking
|
||||
child_db = LockedTable(_child_db)
|
||||
task_db = LockedTable(_task_db)
|
||||
reward_db = LockedTable(_reward_db)
|
||||
image_db = LockedTable(_image_db)
|
||||
pending_reward_db = LockedTable(_pending_rewards_db)
|
||||
users_db = LockedTable(_users_db)
|
||||
|
||||
if os.environ.get('DB_ENV', 'prod') == 'test':
|
||||
child_db.truncate()
|
||||
task_db.truncate()
|
||||
reward_db.truncate()
|
||||
image_db.truncate()
|
||||
pending_reward_db.truncate()
|
||||
users_db.truncate()
|
||||
|
||||
108
backend/db/debug.py
Normal file
108
backend/db/debug.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# python
|
||||
# File: db/debug.py
|
||||
|
||||
import random
|
||||
from models.child import Child
|
||||
from models.task import Task
|
||||
from models.reward import Reward
|
||||
from models.image import Image
|
||||
from db.db import child_db, task_db, reward_db, image_db
|
||||
|
||||
def populate_debug_data(clear_existing=True, seed=42):
|
||||
"""
|
||||
Populate DBs with dummy data:
|
||||
- 2 children
|
||||
- 8 tasks
|
||||
- 4 rewards
|
||||
Each child gets 3 unique tasks and 2 unique rewards (unique within that child).
|
||||
Returns a dict with inserted records.
|
||||
"""
|
||||
random.seed(seed)
|
||||
|
||||
# Optionally clear existing data
|
||||
if clear_existing:
|
||||
try:
|
||||
child_db.truncate()
|
||||
task_db.truncate()
|
||||
reward_db.truncate()
|
||||
image_db.truncate()
|
||||
except Exception:
|
||||
# fallback: remove all docs by id if truncate isn't available
|
||||
from tinydb import Query
|
||||
for doc in child_db.all():
|
||||
child_db.remove(Query().id == doc.get('id'))
|
||||
for doc in task_db.all():
|
||||
task_db.remove(Query().id == doc.get('id'))
|
||||
for doc in reward_db.all():
|
||||
reward_db.remove(Query().id == doc.get('id'))
|
||||
for doc in image_db.all():
|
||||
image_db.remove(Query().id == doc.get('id'))
|
||||
|
||||
# Create 8 tasks
|
||||
task_defs = [
|
||||
("Make Bed", 2, True, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Brush Teeth", 1, True, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Do Homework", 5, True, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Clean Room", 3, True),
|
||||
("Practice Piano", 4, True),
|
||||
("Feed Pet", 1, True, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Take Out Trash", 2, True),
|
||||
("Set Table", 1, True),
|
||||
("Misc Task 1", 1, False),
|
||||
("Misc Task 2", 2, False, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Misc Task 3", 3, False),
|
||||
("Misc Task 4", 4, False, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Misc Task 5", 5, True),
|
||||
]
|
||||
tasks = []
|
||||
for td in task_defs:
|
||||
if len(td) == 4:
|
||||
name, points, is_good, image = td
|
||||
else:
|
||||
name, points, is_good = td
|
||||
image = None
|
||||
t = Task(name=name, points=points, is_good=is_good, image_id=image)
|
||||
task_db.insert(t.to_dict())
|
||||
tasks.append(t.to_dict())
|
||||
|
||||
# Create 4 rewards
|
||||
reward_defs = [
|
||||
("Sticker Pack", "Fun stickers", 3,'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Extra Screen Time", "30 minutes", 8, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("New Toy", "Small toy", 12, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
("Ice Cream", "One scoop", 5, 'a86feb1f-be65-4ca2-bdb5-9223ffbd6779'),
|
||||
]
|
||||
rewards = []
|
||||
for name, desc, cost, image in reward_defs:
|
||||
r = Reward(name=name, description=desc, cost=cost, image_id=image)
|
||||
reward_db.insert(r.to_dict())
|
||||
rewards.append(r.to_dict())
|
||||
|
||||
image_db.insert(Image(1, '.png', True, id='a86feb1f-be65-4ca2-bdb5-9223ffbd6779').to_dict())
|
||||
|
||||
# Create 2 children and assign unique tasks/rewards per child
|
||||
children = []
|
||||
task_ids = [t['id'] for t in tasks]
|
||||
reward_ids = [r['id'] for r in rewards]
|
||||
child_names = [("Child One", 8, "boy01"), ("Child Two", 10, "girl01")]
|
||||
|
||||
for name, age, image in child_names:
|
||||
chosen_tasks = random.sample(task_ids, 11)
|
||||
chosen_rewards = random.sample(reward_ids, 2)
|
||||
points = random.randint(0, 15)
|
||||
c = Child(name=name, age=age, tasks=chosen_tasks, rewards=chosen_rewards, points=points, image_id=image)
|
||||
child_db.insert(c.to_dict())
|
||||
children.append(c.to_dict())
|
||||
|
||||
return {
|
||||
"children": children,
|
||||
"tasks": tasks,
|
||||
"rewards": rewards
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = populate_debug_data(clear_existing=True)
|
||||
print("Inserted debug data:")
|
||||
print(f"Children: {[c['name'] for c in result['children']]}")
|
||||
print(f"Tasks: {[t['name'] for t in result['tasks']]}")
|
||||
print(f"Rewards: {[r['name'] for r in result['rewards']]}")
|
||||
160
backend/db/default.py
Normal file
160
backend/db/default.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# python
|
||||
# File: db/debug.py
|
||||
|
||||
from tinydb import Query
|
||||
|
||||
from api.image_api import IMAGE_TYPE_ICON, IMAGE_TYPE_PROFILE
|
||||
from db.db import task_db, reward_db, image_db
|
||||
from models.image import Image
|
||||
from models.reward import Reward
|
||||
from models.task import Task
|
||||
|
||||
|
||||
def populate_default_data():
|
||||
# Create tasks
|
||||
task_defs = [
|
||||
('default_001', "Be Respectful", 2, True, ''),
|
||||
('default_002', "Brush Teeth", 2, True, ''),
|
||||
('default_003', "Go To Bed", 2, True, ''),
|
||||
('default_004', "Do What You Are Told", 2, True, ''),
|
||||
('default_005', "Make Your Bed", 2, True, ''),
|
||||
('default_006', "Do Homework", 2, True, ''),
|
||||
]
|
||||
tasks = []
|
||||
for _id, name, points, is_good, image in task_defs:
|
||||
t = Task(name=name, points=points, is_good=is_good, image_id=image, id=_id)
|
||||
tq = Query()
|
||||
_result = task_db.search(tq.id == _id)
|
||||
if not _result:
|
||||
task_db.insert(t.to_dict())
|
||||
else:
|
||||
task_db.update(t.to_dict(), tq.id == _id)
|
||||
tasks.append(t.to_dict())
|
||||
|
||||
# Create 4 rewards
|
||||
reward_defs = [
|
||||
('default_001', "Special Trip", "Go to a park or a museum", 3,''),
|
||||
('default_002', "Money", "Money is always nice", 8, ''),
|
||||
('default_003', "Choose Dinner", "What shall we eat?", 12, 'meal'),
|
||||
('default_004', "Tablet Time", "Play your games", 5, 'tablet'),
|
||||
('default_005', "Computer Time", "Minecraft or Roblox?", 5, 'computer-game'),
|
||||
('default_006', "TV Time", "Too much is bad for you.", 5, ''),
|
||||
]
|
||||
rewards = []
|
||||
for _id, name, desc, cost, image in reward_defs:
|
||||
r = Reward(name=name, description=desc, cost=cost, image_id=image, id=_id)
|
||||
rq = Query()
|
||||
_result = reward_db.search(rq.id == _id)
|
||||
if not _result:
|
||||
reward_db.insert(r.to_dict())
|
||||
else:
|
||||
reward_db.update(r.to_dict(), rq.id == _id)
|
||||
rewards.append(r.to_dict())
|
||||
|
||||
image_defs = [
|
||||
('computer-game', IMAGE_TYPE_ICON, '.png', True),
|
||||
('ice-cream', IMAGE_TYPE_ICON, '.png', True),
|
||||
('meal', IMAGE_TYPE_ICON, '.png', True),
|
||||
('playground', IMAGE_TYPE_ICON, '.png', True),
|
||||
('tablet', IMAGE_TYPE_ICON, '.png', True),
|
||||
('boy01', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl01', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl02', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy02', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy03', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl03', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy04', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl04', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
]
|
||||
images = []
|
||||
for _id, _type, ext, perm in image_defs:
|
||||
iq = Query()
|
||||
_result = image_db.search(iq.id == _id)
|
||||
if not _result:
|
||||
image_db.insert(Image(_type, ext, perm, id=_id).to_dict())
|
||||
else:
|
||||
image_db.update(Image(_type, ext, perm, id=_id).to_dict(), iq.id == _id)
|
||||
images.append(Image(_type, ext, perm, id=_id).to_dict())
|
||||
|
||||
return {
|
||||
"images": images,
|
||||
"tasks": tasks,
|
||||
"rewards": rewards
|
||||
}
|
||||
|
||||
def createDefaultTasks():
|
||||
"""Create default tasks if none exist."""
|
||||
if len(task_db.all()) == 0:
|
||||
default_tasks = [
|
||||
Task(name="Take out trash", points=20, is_good=True, image_id="trash-can"),
|
||||
Task(name="Make your bed", points=25, is_good=True, image_id="make-the-bed"),
|
||||
Task(name="Sweep and clean kitchen", points=15, is_good=True, image_id="vacuum"),
|
||||
Task(name="Do homework early", points=30, is_good=True, image_id="homework"),
|
||||
Task(name="Be good for the day", points=15, is_good=True, image_id="good"),
|
||||
Task(name="Clean your mess", points=20, is_good=True, image_id="broom"),
|
||||
|
||||
Task(name="Fighting", points=10, is_good=False, image_id="fighting"),
|
||||
Task(name="Yelling at parents", points=10, is_good=False, image_id="yelling"),
|
||||
Task(name="Lying", points=10, is_good=False, image_id="lying"),
|
||||
Task(name="Not doing what told", points=5, is_good=False, image_id="ignore"),
|
||||
Task(name="Not flushing toilet", points=5, is_good=False, image_id="toilet"),
|
||||
]
|
||||
for task in default_tasks:
|
||||
task_db.insert(task.to_dict())
|
||||
|
||||
def createDefaultRewards():
|
||||
"""Create default rewards if none exist."""
|
||||
if len(reward_db.all()) == 0:
|
||||
default_rewards = [
|
||||
Reward(name="Choose meal", description="Choose dinner or lunch", cost=30, image_id="meal"),
|
||||
Reward(name="$1", description="Money is always nice", cost=35, image_id='money'),
|
||||
Reward(name="$5", description="Even more money", cost=120, image_id='money'),
|
||||
Reward(name="Tablet 1 hour", description="Play your games", cost=50, image_id='tablet'),
|
||||
Reward(name="Computer with dad", description="Let's play a game together", cost=50, image_id='games-with-dad'),
|
||||
Reward(name="Computer 1 hour", description="Minecraft or Terraria?", cost=50, image_id='computer-game'),
|
||||
Reward(name="TV 1 hour", description="Too much is bad for you.", cost=40, image_id='tv'),
|
||||
Reward(name="Candy from store", description="Yum!", cost=70, image_id='candy'),
|
||||
]
|
||||
for reward in default_rewards:
|
||||
reward_db.insert(reward.to_dict())
|
||||
|
||||
def initializeImages():
|
||||
"""Initialize the image database with default images if empty."""
|
||||
if len(image_db.all()) == 0:
|
||||
image_defs = [
|
||||
('boy01', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy02', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy03', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('boy04', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('broom', IMAGE_TYPE_ICON, '.png', True),
|
||||
('candy', IMAGE_TYPE_ICON, '.png', True),
|
||||
('computer-game', IMAGE_TYPE_ICON, '.png', True),
|
||||
('fighting', IMAGE_TYPE_ICON, '.png', True),
|
||||
('games-with-dad', IMAGE_TYPE_ICON, '.png', True),
|
||||
('girl01', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl02', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl03', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('girl04', IMAGE_TYPE_PROFILE, '.png', True),
|
||||
('good', IMAGE_TYPE_ICON, '.png', True),
|
||||
('homework', IMAGE_TYPE_ICON, '.png', True),
|
||||
('ice-cream', IMAGE_TYPE_ICON, '.png', True),
|
||||
('ignore', IMAGE_TYPE_ICON, '.png', True),
|
||||
('lying', IMAGE_TYPE_ICON, '.png', True),
|
||||
('make-the-bed', IMAGE_TYPE_ICON, '.png', True),
|
||||
('meal', IMAGE_TYPE_ICON, '.png', True),
|
||||
('money', IMAGE_TYPE_ICON, '.png', True),
|
||||
('playground', IMAGE_TYPE_ICON, '.png', True),
|
||||
('tablet', IMAGE_TYPE_ICON, '.png', True),
|
||||
('toilet', IMAGE_TYPE_ICON, '.png', True),
|
||||
('trash-can', IMAGE_TYPE_ICON, '.png', True),
|
||||
('tv', IMAGE_TYPE_ICON, '.png', True),
|
||||
('vacuum', IMAGE_TYPE_ICON, '.png', True),
|
||||
('yelling', IMAGE_TYPE_ICON, '.png', True),
|
||||
]
|
||||
for _id, _type, ext, perm in image_defs:
|
||||
img = Image(type=_type, extension=ext, permanent=perm)
|
||||
img.id = _id
|
||||
image_db.insert(img.to_dict())
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = populate_default_data()
|
||||
Reference in New Issue
Block a user