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
+15 -45
View File
@@ -5,39 +5,21 @@ Handles login, logout, and session management
from flask import Blueprint, render_template, request, jsonify, session, redirect, url_for
from werkzeug.security import check_password_hash
from functools import wraps
import json
import os
import data_access as da
import config
auth_bp = Blueprint('auth', __name__)
# Path to users file
USERS_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'users.json')
def load_users():
"""Load users from JSON file"""
if os.path.exists(USERS_FILE):
try:
with open(USERS_FILE, 'r') as f:
return json.load(f)
except:
return []
return []
def login_required(f):
"""Decorator to require login for routes"""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
if 'username' not in session:
return redirect(url_for('auth.login_page'))
# Check if user is active
users = load_users()
if session['user_id'] >= len(users):
session.clear()
return redirect(url_for('auth.login_page', message='User not found'))
user = users[session['user_id']]
if not user.get('active', True):
user = da.get_user_by_username(session['username'])
if not user or not user.get('active', True):
session.clear()
return redirect(url_for('auth.login_page', message='Account is inactive'))
@@ -46,14 +28,10 @@ def login_required(f):
def get_current_user():
"""Get the currently logged in user"""
if 'user_id' not in session:
if 'username' not in session:
return None
users = load_users()
if session['user_id'] >= len(users):
return None
return users[session['user_id']]
return da.get_user_by_username(session['username'])
def can_user(permission, location=None):
"""
@@ -98,14 +76,14 @@ def can_user(permission, location=None):
@auth_bp.route('/login')
def login_page():
"""Serve the login page"""
if 'user_id' in session:
if 'username' in session:
return redirect(url_for('home'))
return render_template('login.html')
@auth_bp.route('/select-location')
def select_location_page():
"""Serve the location selection page"""
if 'user_id' not in session:
if 'username' not in session:
return redirect(url_for('auth.login_page'))
return render_template('select_location.html')
@@ -123,16 +101,8 @@ def login():
'message': 'Username and password are required'
}), 400
users = load_users()
# Find user by username
user_index = None
user = None
for i, u in enumerate(users):
if u['username'] == username:
user_index = i
user = u
break
user = da.get_user_by_username(username)
if not user:
return jsonify({
@@ -155,14 +125,14 @@ def login():
}), 401
# Create session
session['user_id'] = user_index
session['username'] = user['username']
session['defaultLocation'] = user.get('defaultLocation')
# Get accessible locations
accessible_locations = []
if 'locationSettings' in user:
for loc_code, settings in user['locationSettings'].items():
location_settings = user.get('locationSettings', {})
if location_settings:
for loc_code, settings in location_settings.items():
if settings.get('accessible', False):
accessible_locations.append(loc_code)
@@ -190,7 +160,7 @@ def login():
@auth_bp.route('/api/select-location', methods=['POST'])
def select_location():
"""Select a location for the current session"""
if 'user_id' not in session:
if 'username' not in session:
return jsonify({
'status': 'error',
'message': 'Not logged in'
@@ -231,7 +201,7 @@ def logout():
@auth_bp.route('/api/session', methods=['GET'])
def get_session():
"""Get current session information"""
if 'user_id' not in session:
if 'username' not in session:
return jsonify({
'status': 'error',
'message': 'Not logged in'
+113 -2
View File
@@ -2,8 +2,9 @@
Product Finder Blueprint
Handles the product quiz and recommendations
"""
from flask import Blueprint, render_template, send_from_directory, session, jsonify, redirect, url_for
from flask import Blueprint, render_template, send_from_directory, session, jsonify, redirect, url_for, request
from blueprints.auth import login_required, get_current_user, can_user
import data_access as da
import os
products_bp = Blueprint('products', __name__, url_prefix='/quiz')
@@ -47,13 +48,123 @@ def get_user_session():
return jsonify({
'username': session.get('username'),
'userId': session.get('user_id'),
'userId': None, # Not needed with username-based sessions
'currentLocation': session.get('currentLocation'),
'accessibleLocations': session.get('accessibleLocations', []),
'role': session.get('role', 'user'),
'permissions': permissions
})
# Product API Endpoints
@products_bp.route('/api/products', methods=['GET'])
@login_required
def get_products():
"""Get all products or filter by location"""
location = request.args.get('location')
products = da.get_all_products(location=location)
return jsonify({
'status': 'success',
'products': products
})
@products_bp.route('/api/products/<product_code>', methods=['GET'])
@login_required
def get_product(product_code):
"""Get a specific product by code"""
product = da.get_product_by_code(product_code)
if not product:
return jsonify({
'status': 'error',
'message': 'Product not found'
}), 404
return jsonify({
'status': 'success',
'product': product
})
@products_bp.route('/api/products', methods=['POST'])
@login_required
def create_product():
"""Create a new product"""
if not can_user('manage_products'):
return jsonify({
'status': 'error',
'message': 'Permission denied'
}), 403
try:
data = request.json
# Create product via data access layer
product = da.create_product(data)
if not product:
return jsonify({
'status': 'error',
'message': 'Product code already exists'
}), 400
return jsonify({
'status': 'success',
'message': 'Product created successfully',
'product': product
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@products_bp.route('/api/products/<product_code>', methods=['PUT'])
@login_required
def update_product(product_code):
"""Update an existing product"""
if not can_user('manage_products'):
return jsonify({
'status': 'error',
'message': 'Permission denied'
}), 403
try:
data = request.json
# Update product via data access layer
product = da.update_product(product_code, data)
if not product:
return jsonify({
'status': 'error',
'message': 'Product not found'
}), 404
return jsonify({
'status': 'success',
'message': 'Product updated successfully',
'product': product
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@products_bp.route('/api/product-attributes', methods=['GET'])
@login_required
def get_product_attributes():
"""Get product attributes configuration"""
attributes = da.get_product_attributes()
return jsonify({
'status': 'success',
'attributes': attributes
})
@products_bp.route('/css/<path:filename>')
def serve_css(filename):
"""Serve CSS files"""
+35 -87
View File
@@ -3,33 +3,14 @@ User Management Blueprint
Handles user CRUD operations
"""
from flask import Blueprint, render_template, request, jsonify, send_file, session
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.security import check_password_hash
from blueprints.auth import login_required, can_user, get_current_user
import data_access as da
from io import BytesIO
import json
import os
users_bp = Blueprint('users', __name__, url_prefix='/users')
# Path to users file
USERS_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'users.json')
def load_users():
"""Load users from JSON file"""
if os.path.exists(USERS_FILE):
try:
with open(USERS_FILE, 'r') as f:
return json.load(f)
except:
return []
return []
def save_users(users):
"""Save users to JSON file"""
os.makedirs(os.path.dirname(USERS_FILE), exist_ok=True)
with open(USERS_FILE, 'w') as f:
json.dump(users, f, indent=2)
def permission_required(permission):
"""Decorator to require specific permission"""
from functools import wraps
@@ -52,15 +33,14 @@ def user_manager():
"""Serve the user management page"""
if not can_user('manage_users'):
return "<h1>403 - Permission Denied</h1><p>You don't have permission to manage users.</p>", 403
current_user_id = session.get('user_id', -1)
return render_template('user_manager.html', current_user_id=current_user_id)
return render_template('user_manager.html')
@users_bp.route('/api', methods=['GET'])
@login_required
@permission_required('manage_users')
def get_users():
"""Get all users"""
users = load_users()
users = da.get_all_users()
return jsonify({
'status': 'success',
'users': users
@@ -81,40 +61,16 @@ def add_user():
'message': 'Username, password, and default location are required'
}), 400
users = load_users()
# Create user via data access layer
new_user = da.create_user(data)
# Check if username already exists
if any(user['username'] == data['username'] for user in users):
if not new_user:
return jsonify({
'status': 'error',
'message': 'Username already exists'
}), 400
# Hash the password
hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256')
# Get location settings
location_settings = data.get('locationSettings', {})
default_location = 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': data['username'],
'password': hashed_password,
'defaultLocation': default_location,
'locationSettings': location_settings,
'permissions': data.get('permissions', {}),
'active': data.get('active', True)
}
users.append(new_user)
save_users(users)
users = da.get_all_users()
return jsonify({
'status': 'success',
'message': 'User added successfully',
@@ -122,28 +78,25 @@ def add_user():
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@users_bp.route('/api/<int:user_index>', methods=['DELETE'])
@users_bp.route('/api/<int:user_id>', methods=['DELETE'])
@login_required
@permission_required('manage_users')
def delete_user(user_index):
"""Delete a specific user by index"""
def delete_user(user_id):
"""Delete a specific user by ID"""
try:
users = load_users()
if user_index < 0 or user_index >= len(users):
if not da.delete_user(user_id):
return jsonify({
'status': 'error',
'message': 'Invalid user index'
}), 400
users.pop(user_index)
save_users(users)
'message': 'User not found'
}), 404
users = da.get_all_users()
return jsonify({
'status': 'success',
'message': 'User deleted successfully',
@@ -151,29 +104,28 @@ def delete_user(user_index):
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@users_bp.route('/api/<int:user_index>/active', methods=['PATCH'])
@users_bp.route('/api/<int:user_id>/active', methods=['PATCH'])
@login_required
@permission_required('manage_users')
def toggle_user_active(user_index):
def toggle_user_active(user_id):
"""Toggle user active status"""
try:
data = request.json
users = load_users()
user = da.toggle_user_active(user_id, data.get('active', True))
if user_index < 0 or user_index >= len(users):
if not user:
return jsonify({
'status': 'error',
'message': 'Invalid user index'
}), 400
users[user_index]['active'] = data.get('active', True)
save_users(users)
'message': 'User not found'
}), 404
users = da.get_all_users()
return jsonify({
'status': 'success',
'message': 'User status updated',
@@ -181,15 +133,16 @@ def toggle_user_active(user_index):
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
}), 500
@users_bp.route('/api/<int:user_index>/change-password', methods=['POST'])
@users_bp.route('/api/<int:user_id>/change-password', methods=['POST'])
@login_required
@permission_required('manage_users')
def change_user_password(user_index):
def change_user_password(user_id):
"""Change a user's password with current user verification"""
try:
data = request.json
@@ -222,26 +175,21 @@ def change_user_password(user_index):
'message': 'Your password is incorrect. Password change denied for security reasons.'
}), 403
# Load users and validate index
users = load_users()
if user_index < 0 or user_index >= len(users):
# Update password via data access layer
updated_user = da.change_user_password(user_id, new_password)
if not updated_user:
return jsonify({
'status': 'error',
'message': 'Invalid user index'
}), 400
# Update password
hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256')
target_username = users[user_index]['username']
users[user_index]['password'] = hashed_password
save_users(users)
'message': 'User not found'
}), 404
return jsonify({
'status': 'success',
'message': f'Password changed successfully for user "{target_username}"'
'message': f'Password changed successfully for user "{updated_user["username"]}"'
})
except Exception as e:
da.rollback()
return jsonify({
'status': 'error',
'message': str(e)
@@ -253,7 +201,7 @@ def change_user_password(user_index):
def download_users():
"""Download users as JSON file"""
try:
users = load_users()
users = da.get_all_users()
if not users:
return jsonify({