added user images partitioning

This commit is contained in:
2025-12-12 16:17:23 -05:00
parent 9b90ca12fb
commit f8d709f292
22 changed files with 91 additions and 42 deletions

2
.gitignore vendored
View File

@@ -40,7 +40,7 @@ env/
*.db
*.sqlite
*.sqlite3
db/*.json
data/db/*.json
# Flask
instance/

View File

@@ -1,15 +1,15 @@
import os
import uuid
from io import BytesIO
from PIL import Image as PILImage, UnidentifiedImageError
from flask import Blueprint, request, jsonify, send_file
from tinydb import Query
from config.paths import get_user_image_dir
from db.db import image_db
from models.image import Image
from tinydb import Query
from PIL import Image as PILImage, UnidentifiedImageError
image_api = Blueprint('image_api', __name__)
UPLOAD_FOLDER = './resources/images'
UPLOAD_FOLDER = get_user_image_dir("user123")
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png'}
IMAGE_TYPE_PROFILE = 1
IMAGE_TYPE_ICON = 2
@@ -61,7 +61,7 @@ def upload():
format_extension_map = {'JPEG': '.jpg', 'PNG': '.png'}
extension = format_extension_map.get(original_format, '.png')
image_record = Image(extension=extension, permanent=perm, type=image_type)
image_record = Image(extension=extension, permanent=perm, type=image_type, user="user123")
filename = image_record.id + extension
filepath = os.path.abspath(os.path.join(UPLOAD_FOLDER, filename))
@@ -83,11 +83,11 @@ def upload():
@image_api.route('/image/request/<id>', methods=['GET'])
def request_image(id):
ImageQuery = Query()
image = image_db.get(ImageQuery.id == id)
image: Image = Image.from_dict(image_db.get(ImageQuery.id == id))
if not image:
return jsonify({'error': 'Image not found'}), 404
filename = f"{image['id']}{image['extension']}"
filepath = os.path.abspath(os.path.join(UPLOAD_FOLDER, filename))
filename = f"{image.id}{image.extension}"
filepath = os.path.abspath(os.path.join(get_user_image_dir(image.user), filename))
if not os.path.exists(filepath):
return jsonify({'error': 'File not found'}), 404
return send_file(filepath)

27
config/paths.py Normal file
View File

@@ -0,0 +1,27 @@
# python
# file: config/paths.py
import os
# Constant directory names
DATA_DIR_NAME = 'data'
TEST_DATA_DIR_NAME = 'test_data'
# Project root (two levels up from this file)
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def get_database_dir(db_env: str | None = None) -> str:
"""
Return the absolute base directory path for the given DB env.
db_env: 'prod' uses `data/db`, anything else uses `test_data/db`.
"""
env = (db_env or os.environ.get('DB_ENV', 'prod')).lower()
base_name = DATA_DIR_NAME if env == 'prod' else TEST_DATA_DIR_NAME
return os.path.join(PROJECT_ROOT, base_name, 'db')
def get_user_image_dir(username: str | None) -> str:
"""
Return the absolute directory path for storing images for a specific user.
"""
if username:
return os.path.join(PROJECT_ROOT, DATA_DIR_NAME, 'images', username)
return os.path.join(PROJECT_ROOT, 'resources', 'images')

View File

@@ -1,18 +1,10 @@
# python
import os
from config.paths import get_database_dir
import threading
from tinydb import TinyDB
DB_ENV = os.environ.get('DB_ENV', 'prod')
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if DB_ENV == 'prod':
base_dir = os.path.join(project_root, 'data/db')
else:
base_dir = os.path.join(project_root, 'test_data/db')
base_dir = get_database_dir()
os.makedirs(base_dir, exist_ok=True)
@@ -95,7 +87,7 @@ reward_db = LockedTable(_reward_db)
image_db = LockedTable(_image_db)
pending_reward_db = LockedTable(_pending_rewards_db)
if DB_ENV == 'test':
if os.environ.get('DB_ENV', 'prod') == 'test':
child_db.truncate()
task_db.truncate()
reward_db.truncate()

View File

@@ -1,14 +1,14 @@
# 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 tinydb import Query
from db.db import child_db, task_db, reward_db, image_db
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
@@ -86,19 +86,33 @@ def initializeImages():
"""Initialize the image database with default images if empty."""
if len(image_db.all()) == 0:
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),
('broom', 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)

View File

@@ -1,5 +1,5 @@
import sys, logging
import sys, logging, os
from config.paths import get_user_image_dir
from flask import Flask, request
from flask_cors import CORS
@@ -55,7 +55,8 @@ def start_background_threads():
broadcaster.daemon = True
broadcaster.start()
# Initialize background workers on server start
# TODO: implement users
os.makedirs(get_user_image_dir("user123"), exist_ok=True)
initializeImages()
start_background_threads()

View File

@@ -7,6 +7,7 @@ class Image(BaseModel):
type: int
extension: str
permanent: bool = False
user: str | None = None
@classmethod
def from_dict(cls, d: dict):
@@ -17,7 +18,8 @@ class Image(BaseModel):
permanent=d.get('permanent', False),
id=d.get('id'),
created_at=d.get('created_at'),
updated_at=d.get('updated_at')
updated_at=d.get('updated_at'),
user=d.get('user')
)
def to_dict(self):
@@ -25,6 +27,7 @@ class Image(BaseModel):
base.update({
'type': self.type,
'permanent': self.permanent,
'extension': self.extension
'extension': self.extension,
'user': self.user
})
return base

BIN
resources/images/broom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
resources/images/good.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
resources/images/ignore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
resources/images/lying.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
resources/images/money.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
resources/images/toilet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
resources/images/tv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
resources/images/vacuum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -159,7 +159,7 @@ const showBack = computed(
/* top bar holds title and logout button */
.topbar {
display: grid;
grid-template-columns: 76px 1fr 76px;
grid-template-columns: 46px 1fr 46px;
align-items: center;
padding: 5px 5px;
}
@@ -235,7 +235,11 @@ const showBack = computed(
@media (max-width: 480px) {
.back-btn {
padding: 0.45rem 0.75rem;
font-size: 0.95rem;
font-size: 0.6rem;
height: 100%;
}
.login-btn {
height: 100%;
}
}
</style>
@@ -250,4 +254,12 @@ const showBack = computed(
color: #667eea;
font-weight: 600;
}
@media (max-width: 480px) {
.login-btn button {
padding: 0.45rem 0.75rem;
font-size: 0.6rem;
height: 100%;
}
}
</style>