SQLite and JSON toggle support added

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Jason
2026-05-05 16:06:52 -05:00
parent 29154bd651
commit 0632aa22e1
12 changed files with 1862 additions and 136 deletions
+449
View File
@@ -0,0 +1,449 @@
"""
JSON Data Access Layer
Provides data operations using JSON files
"""
import json
import os
from werkzeug.security import generate_password_hash
from threading import Lock
# File paths
basedir = os.path.abspath(os.path.dirname(__file__))
USERS_FILE = os.path.join(basedir, 'data', 'users.json')
PRODUCTS_FILE = os.path.join(basedir, 'data', 'products.json')
PRODUCT_ATTRIBUTES_FILE = os.path.join(basedir, 'data', 'product_attributes.json')
# Thread locks for file operations
users_lock = Lock()
products_lock = Lock()
attributes_lock = Lock()
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def _load_json_file(filepath):
"""Load a JSON file"""
if os.path.exists(filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return []
return []
def _save_json_file(filepath, data):
"""Save data to a JSON file"""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
def _find_user_by_username(users, username):
"""Find user index by username"""
for i, user in enumerate(users):
if user['username'] == username:
return i, user
return None, None
def _find_product_by_code(products, product_code):
"""Find product index by code"""
for i, product in enumerate(products):
if product['productCode'] == product_code:
return i, product
return None, None
# ============================================================================
# USER OPERATIONS
# ============================================================================
def get_user_by_username(username):
"""Get a user by username"""
with users_lock:
users = _load_json_file(USERS_FILE)
_, user = _find_user_by_username(users, username)
return user
def get_user_by_id(user_id):
"""Get a user by ID (array index in JSON)"""
with users_lock:
users = _load_json_file(USERS_FILE)
if 0 <= user_id < len(users):
return users[user_id]
return None
def get_all_users():
"""Get all users"""
with users_lock:
return _load_json_file(USERS_FILE)
def create_user(user_data):
"""
Create a new user
Args:
user_data (dict): User data with keys: username, password, defaultLocation,
locationSettings, permissions, active
Returns:
dict: Created user as dictionary or None if username exists
"""
with users_lock:
users = _load_json_file(USERS_FILE)
# Check if username already exists
if any(user['username'] == user_data['username'] for user in users):
return None
# Hash password if not already hashed
password = user_data['password']
if not password.startswith('pbkdf2:'):
password = generate_password_hash(password, method='pbkdf2:sha256')
# Get location settings
location_settings = user_data.get('locationSettings', {})
default_location = user_data['defaultLocation']
# Ensure default location is accessible
if default_location not in location_settings:
location_settings[default_location] = {}
location_settings[default_location]['accessible'] = True
# Create new user
new_user = {
'username': user_data['username'],
'password': password,
'defaultLocation': default_location,
'locationSettings': location_settings,
'permissions': user_data.get('permissions', {}),
'active': user_data.get('active', True)
}
if user_data.get('superAdmin'):
new_user['superAdmin'] = True
users.append(new_user)
_save_json_file(USERS_FILE, users)
return new_user
def update_user(user_id, user_data):
"""
Update an existing user
Args:
user_id (int): User ID (array index)
user_data (dict): Updated user data
Returns:
dict: Updated user as dictionary or None if not found
"""
with users_lock:
users = _load_json_file(USERS_FILE)
if user_id < 0 or user_id >= len(users):
return None
user = users[user_id]
# Update fields
if 'defaultLocation' in user_data:
user['defaultLocation'] = user_data['defaultLocation']
if 'active' in user_data:
user['active'] = user_data['active']
if 'superAdmin' in user_data:
user['superAdmin'] = user_data['superAdmin']
if 'locationSettings' in user_data:
user['locationSettings'] = user_data['locationSettings']
if 'permissions' in user_data:
user['permissions'] = user_data['permissions']
if 'password' in user_data:
password = user_data['password']
if not password.startswith('pbkdf2:'):
password = generate_password_hash(password, method='pbkdf2:sha256')
user['password'] = password
_save_json_file(USERS_FILE, users)
return user
def delete_user(user_id):
"""
Delete a user
Args:
user_id (int): User ID (array index)
Returns:
bool: True if deleted, False if not found
"""
with users_lock:
users = _load_json_file(USERS_FILE)
if user_id < 0 or user_id >= len(users):
return False
users.pop(user_id)
_save_json_file(USERS_FILE, users)
return True
def toggle_user_active(user_id, active):
"""
Toggle user active status
Args:
user_id (int): User ID (array index)
active (bool): Active status
Returns:
dict: Updated user or None if not found
"""
with users_lock:
users = _load_json_file(USERS_FILE)
if user_id < 0 or user_id >= len(users):
return None
users[user_id]['active'] = active
_save_json_file(USERS_FILE, users)
return users[user_id]
def change_user_password(user_id, new_password):
"""
Change a user's password
Args:
user_id (int): User ID (array index)
new_password (str): New password (will be hashed)
Returns:
dict: Updated user or None if not found
"""
with users_lock:
users = _load_json_file(USERS_FILE)
if user_id < 0 or user_id >= len(users):
return None
hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256')
users[user_id]['password'] = hashed_password
_save_json_file(USERS_FILE, users)
return users[user_id]
# ============================================================================
# PRODUCT OPERATIONS
# ============================================================================
def get_all_products(location=None):
"""
Get all products, optionally filtered by location
Args:
location (str, optional): Filter by location code
Returns:
list: List of product dictionaries
"""
with products_lock:
products = _load_json_file(PRODUCTS_FILE)
if location:
products = [p for p in products if p.get('location') == location]
return products
def get_product_by_code(product_code):
"""
Get a product by its product code
Args:
product_code (str): Product code
Returns:
dict: Product dictionary or None if not found
"""
with products_lock:
products = _load_json_file(PRODUCTS_FILE)
_, product = _find_product_by_code(products, product_code)
return product
def create_product(product_data):
"""
Create a new product
Args:
product_data (dict): Product data
Returns:
dict: Created product or None if code exists
"""
with products_lock:
products = _load_json_file(PRODUCTS_FILE)
# Check if product already exists
if any(p['productCode'] == product_data['productCode'] for p in products):
return None
# Create product with normalized structure
new_product = {
'id': product_data['productCode'],
'productCode': product_data['productCode'],
'category': product_data.get('category'),
'description': product_data.get('description'),
'discontinued': product_data.get('discontinued', False),
'location': product_data.get('location'),
'baseType': product_data.get('baseType', ''),
'subType': product_data.get('subType', {}),
'materials': product_data.get('materials', []),
'colors': product_data.get('colors', []),
'isAccessory': product_data.get('isAccessory', False),
'compatibleAccessories': product_data.get('compatibleAccessories', []),
'imageConfig': product_data.get('imageConfig', {})
}
products.append(new_product)
_save_json_file(PRODUCTS_FILE, products)
return new_product
def update_product(product_code, product_data):
"""
Update an existing product
Args:
product_code (str): Product code
product_data (dict): Updated product data
Returns:
dict: Updated product or None if not found
"""
with products_lock:
products = _load_json_file(PRODUCTS_FILE)
index, product = _find_product_by_code(products, product_code)
if product is None:
return None
# Update fields
if 'category' in product_data:
product['category'] = product_data['category']
if 'description' in product_data:
product['description'] = product_data['description']
if 'discontinued' in product_data:
product['discontinued'] = product_data['discontinued']
if 'location' in product_data:
product['location'] = product_data['location']
if 'baseType' in product_data:
product['baseType'] = product_data['baseType']
if 'isAccessory' in product_data:
product['isAccessory'] = product_data['isAccessory']
if 'subType' in product_data:
product['subType'] = product_data['subType']
if 'materials' in product_data:
product['materials'] = product_data['materials']
if 'colors' in product_data:
product['colors'] = product_data['colors']
if 'compatibleAccessories' in product_data:
product['compatibleAccessories'] = product_data['compatibleAccessories']
if 'imageConfig' in product_data:
product['imageConfig'] = product_data['imageConfig']
_save_json_file(PRODUCTS_FILE, products)
return product
def delete_product(product_code):
"""
Delete a product
Args:
product_code (str): Product code
Returns:
bool: True if deleted, False if not found
"""
with products_lock:
products = _load_json_file(PRODUCTS_FILE)
index, _ = _find_product_by_code(products, product_code)
if index is None:
return False
products.pop(index)
_save_json_file(PRODUCTS_FILE, products)
return True
# ============================================================================
# LOCATION OPERATIONS
# ============================================================================
def get_all_locations():
"""Get all locations"""
attributes = _load_json_file(PRODUCT_ATTRIBUTES_FILE)
return attributes.get('locations', [])
def get_location_by_code(code):
"""Get a location by code"""
locations = get_all_locations()
for loc in locations:
if loc['code'] == code:
return loc
return None
# ============================================================================
# PRODUCT ATTRIBUTES OPERATIONS
# ============================================================================
def get_product_attributes():
"""
Get product attributes configuration
Returns:
dict: Attributes with locations, statuses, productTypes, codeModifiers
"""
with attributes_lock:
attributes = _load_json_file(PRODUCT_ATTRIBUTES_FILE)
if not attributes:
return {
'locations': [],
'statuses': [],
'productTypes': [],
'codeModifiers': []
}
return attributes
def update_product_attributes(attributes_data):
"""
Update product attributes configuration
Args:
attributes_data (dict): Updated attributes
Returns:
dict: Updated attributes
"""
with attributes_lock:
attributes = _load_json_file(PRODUCT_ATTRIBUTES_FILE)
if 'statuses' in attributes_data:
attributes['statuses'] = attributes_data['statuses']
if 'productTypes' in attributes_data:
attributes['productTypes'] = attributes_data['productTypes']
if 'codeModifiers' in attributes_data:
attributes['codeModifiers'] = attributes_data['codeModifiers']
if 'locations' in attributes_data:
attributes['locations'] = attributes_data['locations']
_save_json_file(PRODUCT_ATTRIBUTES_FILE, attributes)
return attributes
# ============================================================================
# UTILITY FUNCTIONS
# ============================================================================
def commit():
"""No-op for JSON (writes are immediate)"""
pass
def rollback():
"""No-op for JSON (no transactions)"""
pass