From 518c9478dce022a6e510705ea08838d22749174c Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 23 Jan 2026 01:06:30 -0600 Subject: [PATCH] V1.0.0.2 - Refactor: Moved user Management out of App.py and into users.py --- __pycache__/utils.cpython-313.pyc | Bin 0 -> 1950 bytes app.py | 285 +------------------ blueprints/__pycache__/users.cpython-313.pyc | Bin 0 -> 8558 bytes blueprints/users.py | 194 +++++++++++++ templates/base.html | 2 +- utils.py | 31 ++ 6 files changed, 237 insertions(+), 275 deletions(-) create mode 100644 __pycache__/utils.cpython-313.pyc create mode 100644 blueprints/__pycache__/users.cpython-313.pyc create mode 100644 blueprints/users.py create mode 100644 utils.py diff --git a/__pycache__/utils.cpython-313.pyc b/__pycache__/utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2082dbf37bc968a674d497deed8f475de76c1613 GIT binary patch literal 1950 zcmcIl%}*Og6rWx1`U`^{3^5Hf89s}nhJs>8qNTJ|1EeM;32Un#MkUMc+PFAgn6aC} z38@#PUUI2Kr5tj|AxM?lWB-I6dRSyGU6FpMx%5(@x{=zR`rg=v(314jk@n5oeKYU9 zdGq_dt+loW35+kEkion+?)wGZ?MSYAo9U~3M{#}J;=Ev&Ts2qJ`LgS0mRVqy zQFeV5R$R>4EH1hJwTi{o7tJL@!u^fmff=9LD2NXrx$L1eO_B{gLlTlEogp9klZ|a9 zDI*P#xV#aYx7I2Uix%yCp+q4RWp<@(8I6TvQbC@Br?w1YQi2LdSz$o&3Ix$9Az4gA z3{BQN$wtj6$>MG?vc9IKDb3WH^8iW9eI!FhNk+OXUBcwlIXFJI*kZx_tM_`*(z(cz zLYifp>V^SDqvhoSr>lD2a(%aTRw|V8t}2egFl^l8Dmu$^*Si8%n57)YG%4M5`;C_u zv~*^Adg1!~wbay`X>CC>^wL$^URkJ=3q?M%y6(1{mSHo!Y?+I>O35hWC7l=k7g@b< z=Ey|RHuNH&9QpTJJTSim;*U-8V@t=~nQBY_e(03bp8RTkFZ9NKwEOPNy_ssyi`D1_ zCv@SvaOBQ_6Ykv}+70(Q{@xnwBn%Ud%Pfj|CkT$Ey-8UIOm5rpWLOt`#FO)05D@i| z8r&NDwJL2gGJBOI*Yhs44T1FvP zFaR<|YnaEMfX^Wc!3`b&v#%vUOiE8jX+}=SC*ySPSdw1wBk=euI+Xt(JZ1ynTntS; zLc1nmbzhP+ujU)~l;n&&+*qZ<`i!jk(VkXLU`0R+{s${snrvv!zzWYlSP`0Itq_5= zp$MahfY=z;rmg~p#ZA!Z`D<^}3{Z#Dcdks$PtgXA=&SU#G_(?rU9wySFKuJwJ-b3p zo0jY{UDiLc=&HqT6+|~Wk!4;}FXb)f225yFOSaC;xXSQ87@~&xQDEU1c2h8&LcJY? zt6&lW#jc8>Imb#-pyH;0vcR@Gfu^T(%Vy7_8^+-ugFw24yYBm{;TQHJozCg;ugCWy zQ~R-V_k9n1RXSRYjX9CAN6~JlXZqWibL0Kpx3bQa1!pO@8_jQB{yrMpPTcc;KJlPZ z?M?^})8F{_A~)(DTBP;') @@ -566,45 +533,6 @@ def get_location_scans(location_count_id): return jsonify({'success': False, 'message': str(e)}) - - """Start counting a location""" - location_code = request.form.get('location_code', '').strip().upper() - - if not location_code: - return jsonify({'success': False, 'message': 'Location code required'}) - - # Check if location already being counted - existing = query_db(''' - SELECT * FROM LocationCounts - WHERE session_id = ? AND location_name = ? AND status = 'in_progress' - ''', [session_id, location_code], one=True) - - if existing: - return jsonify({ - 'success': False, - 'message': f'Location {location_code} is already being counted by another user' - }) - - # Check expected lots from MASTER baseline - expected_lots = query_db(''' - SELECT COUNT(*) as count FROM BaselineInventory_Master - WHERE session_id = ? AND system_bin = ? - ''', [session_id, location_code], one=True)['count'] - - # Create location count record - location_count_id = execute_db(''' - INSERT INTO LocationCounts - (session_id, location_name, counted_by, status, expected_lots_master) - VALUES (?, ?, ?, 'in_progress', ?) - ''', [session_id, location_code, session['user_id'], expected_lots]) - - return jsonify({ - 'success': True, - 'location_count_id': location_count_id, - 'redirect': url_for('count_location', session_id=session_id, location_count_id=location_count_id) - }) - - @app.route('/count//location/') @login_required def count_location(session_id, location_count_id): @@ -1117,197 +1045,6 @@ def finish_location(session_id, location_count_id): }) -# ==================== ROUTES: USER MANAGEMENT ==================== - -@app.route('/settings/users') -@role_required('owner', 'admin') -def manage_users(): - """User management page""" - # Get all users - if session['role'] == 'owner': - # Owners can see everyone - users = query_db('SELECT * FROM Users ORDER BY role, full_name') - else: - # Admins can only see staff - users = query_db("SELECT * FROM Users WHERE role = 'staff' ORDER BY full_name") - - return render_template('manage_users.html', users=users) - - -@app.route('/settings/users/add', methods=['POST']) -@role_required('owner', 'admin') -def add_user(): - """Add a new user""" - data = request.get_json() - - username = data.get('username', '').strip() - password = data.get('password', '') - first_name = data.get('first_name', '').strip() - last_name = data.get('last_name', '').strip() - email = data.get('email', '').strip() - role = data.get('role', 'staff') - branch = data.get('branch', 'Main') - - # Validation - if not username or not password or not first_name or not last_name: - return jsonify({'success': False, 'message': 'Username, password, first name, and last name are required'}) - - # Admins can't create admins or owners - if session['role'] == 'admin' and role in ['admin', 'owner']: - return jsonify({'success': False, 'message': 'Permission denied: Admins can only create Staff users'}) - - # Check if username exists - existing = query_db('SELECT user_id FROM Users WHERE username = ?', [username], one=True) - if existing: - return jsonify({'success': False, 'message': 'Username already exists'}) - - # Create user - full_name = f"{first_name} {last_name}" - hashed_password = generate_password_hash(password) - - try: - execute_db(''' - INSERT INTO Users (username, password, full_name, role, branch, is_active) - VALUES (?, ?, ?, ?, ?, 1) - ''', [username, hashed_password, full_name, role, branch]) - - return jsonify({'success': True, 'message': 'User created successfully'}) - except Exception as e: - return jsonify({'success': False, 'message': f'Error creating user: {str(e)}'}) - - -@app.route('/settings/users/', methods=['GET']) -@role_required('owner', 'admin') -def get_user(user_id): - """Get user details""" - user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) - - if not user: - return jsonify({'success': False, 'message': 'User not found'}) - - # Admins can't view other admins or owners - if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: - return jsonify({'success': False, 'message': 'Permission denied'}) - - # Split full name - name_parts = user['full_name'].split(' ', 1) - first_name = name_parts[0] if len(name_parts) > 0 else '' - last_name = name_parts[1] if len(name_parts) > 1 else '' - - # Get email, handle None - email = user['email'] if user['email'] else '' - - return jsonify({ - 'success': True, - 'user': { - 'user_id': user['user_id'], - 'username': user['username'], - 'first_name': first_name, - 'last_name': last_name, - 'email': email, - 'role': user['role'], - 'branch': user['branch'], - 'is_active': user['is_active'] - } - }) - - -@app.route('/settings/users//update', methods=['POST']) -@role_required('owner', 'admin') -def update_user(user_id): - """Update user details""" - data = request.get_json() - - # Get existing user - user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) - - if not user: - return jsonify({'success': False, 'message': 'User not found'}) - - # Admins can't edit other admins or owners - if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: - return jsonify({'success': False, 'message': 'Permission denied'}) - - # Can't edit yourself to change your own role or deactivate - if user_id == session['user_id']: - if data.get('role') != user['role']: - return jsonify({'success': False, 'message': 'Cannot change your own role'}) - if data.get('is_active') == 0: - return jsonify({'success': False, 'message': 'Cannot deactivate yourself'}) - - # Build update - username = data.get('username', '').strip() - first_name = data.get('first_name', '').strip() - last_name = data.get('last_name', '').strip() - email = data.get('email', '').strip() - role = data.get('role', user['role']) - branch = data.get('branch', user['branch']) - is_active = data.get('is_active', user['is_active']) - password = data.get('password', '').strip() - - if not username or not first_name or not last_name: - return jsonify({'success': False, 'message': 'Username, first name, and last name are required'}) - - # Check if username is taken by another user - if username != user['username']: - existing = query_db('SELECT user_id FROM Users WHERE username = ? AND user_id != ?', [username, user_id], one=True) - if existing: - return jsonify({'success': False, 'message': 'Username already taken'}) - - # Admins can't change role to admin or owner - if session['role'] == 'admin' and role in ['admin', 'owner']: - return jsonify({'success': False, 'message': 'Permission denied: Cannot assign Admin or Owner role'}) - - full_name = f"{first_name} {last_name}" - - try: - if password: - # Update with new password - hashed_password = generate_password_hash(password) - execute_db(''' - UPDATE Users - SET username = ?, full_name = ?, email = ?, role = ?, branch = ?, is_active = ?, password = ? - WHERE user_id = ? - ''', [username, full_name, email, role, branch, is_active, hashed_password, user_id]) - else: - # Update without changing password - execute_db(''' - UPDATE Users - SET username = ?, full_name = ?, email = ?, role = ?, branch = ?, is_active = ? - WHERE user_id = ? - ''', [username, full_name, email, role, branch, is_active, user_id]) - - return jsonify({'success': True, 'message': 'User updated successfully'}) - except Exception as e: - return jsonify({'success': False, 'message': f'Error updating user: {str(e)}'}) - - -@app.route('/settings/users//delete', methods=['POST']) -@role_required('owner', 'admin') -def delete_user(user_id): - """Delete (deactivate) a user""" - # Get user - user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) - - if not user: - return jsonify({'success': False, 'message': 'User not found'}) - - # Admins can't delete other admins or owners - if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: - return jsonify({'success': False, 'message': 'Permission denied'}) - - # Can't delete yourself - if user_id == session['user_id']: - return jsonify({'success': False, 'message': 'Cannot delete yourself'}) - - # Soft delete (deactivate) - try: - execute_db('UPDATE Users SET is_active = 0 WHERE user_id = ?', [user_id]) - return jsonify({'success': True, 'message': 'User deleted successfully'}) - except Exception as e: - return jsonify({'success': False, 'message': f'Error deleting user: {str(e)}'}) - - # ==================== PWA SUPPORT ROUTES ==================== @app.route('/manifest.json') @@ -1323,4 +1060,4 @@ def serve_sw(): # ==================== RUN APPLICATION ==================== if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=5000) + app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/blueprints/__pycache__/users.cpython-313.pyc b/blueprints/__pycache__/users.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0de51c9ea2e5e12140b9473cc383034d764baba3 GIT binary patch literal 8558 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 b)^|;S8hx@eW~;odv0`hj-X{+7fV%MyjRzgJ literal 0 HcmV?d00001 diff --git a/blueprints/users.py b/blueprints/users.py new file mode 100644 index 0000000..47c1acd --- /dev/null +++ b/blueprints/users.py @@ -0,0 +1,194 @@ +from flask import Blueprint, render_template, request, jsonify, session +from werkzeug.security import generate_password_hash +from db import query_db, execute_db +from utils import role_required + +users_bp = Blueprint('users', __name__) + +@users_bp.route('/settings/users') +@role_required('owner', 'admin') +def manage_users(): + """User management page""" + # Get all users + if session['role'] == 'owner': + # Owners can see everyone + users = query_db('SELECT * FROM Users ORDER BY role, full_name') + else: + # Admins can only see staff + users = query_db("SELECT * FROM Users WHERE role = 'staff' ORDER BY full_name") + + return render_template('manage_users.html', users=users) + + +@users_bp.route('/settings/users/add', methods=['POST']) +@role_required('owner', 'admin') +def add_user(): + """Add a new user""" + data = request.get_json() + + username = data.get('username', '').strip() + password = data.get('password', '') + first_name = data.get('first_name', '').strip() + last_name = data.get('last_name', '').strip() + email = data.get('email', '').strip() + role = data.get('role', 'staff') + branch = data.get('branch', 'Main') + + # Validation + if not username or not password or not first_name or not last_name: + return jsonify({'success': False, 'message': 'Username, password, first name, and last name are required'}) + + # Admins can't create admins or owners + if session['role'] == 'admin' and role in ['admin', 'owner']: + return jsonify({'success': False, 'message': 'Permission denied: Admins can only create Staff users'}) + + # Check if username exists + existing = query_db('SELECT user_id FROM Users WHERE username = ?', [username], one=True) + if existing: + return jsonify({'success': False, 'message': 'Username already exists'}) + + # Create user + full_name = f"{first_name} {last_name}" + hashed_password = generate_password_hash(password) + + try: + execute_db(''' + INSERT INTO Users (username, password, full_name, role, branch, is_active) + VALUES (?, ?, ?, ?, ?, 1) + ''', [username, hashed_password, full_name, role, branch]) + + return jsonify({'success': True, 'message': 'User created successfully'}) + except Exception as e: + return jsonify({'success': False, 'message': f'Error creating user: {str(e)}'}) + + +@users_bp.route('/settings/users/', methods=['GET']) +@role_required('owner', 'admin') +def get_user(user_id): + """Get user details""" + user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) + + if not user: + return jsonify({'success': False, 'message': 'User not found'}) + + # Admins can't view other admins or owners + if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: + return jsonify({'success': False, 'message': 'Permission denied'}) + + # Split full name + name_parts = user['full_name'].split(' ', 1) + first_name = name_parts[0] if len(name_parts) > 0 else '' + last_name = name_parts[1] if len(name_parts) > 1 else '' + + # Get email, handle None + email = user['email'] if user['email'] else '' + + return jsonify({ + 'success': True, + 'user': { + 'user_id': user['user_id'], + 'username': user['username'], + 'first_name': first_name, + 'last_name': last_name, + 'email': email, + 'role': user['role'], + 'branch': user['branch'], + 'is_active': user['is_active'] + } + }) + + +@users_bp.route('/settings/users//update', methods=['POST']) +@role_required('owner', 'admin') +def update_user(user_id): + """Update user details""" + data = request.get_json() + + # Get existing user + user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) + + if not user: + return jsonify({'success': False, 'message': 'User not found'}) + + # Admins can't edit other admins or owners + if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: + return jsonify({'success': False, 'message': 'Permission denied'}) + + # Can't edit yourself to change your own role or deactivate + if user_id == session['user_id']: + if data.get('role') != user['role']: + return jsonify({'success': False, 'message': 'Cannot change your own role'}) + if data.get('is_active') == 0: + return jsonify({'success': False, 'message': 'Cannot deactivate yourself'}) + + # Build update + username = data.get('username', '').strip() + first_name = data.get('first_name', '').strip() + last_name = data.get('last_name', '').strip() + email = data.get('email', '').strip() + role = data.get('role', user['role']) + branch = data.get('branch', user['branch']) + is_active = data.get('is_active', user['is_active']) + password = data.get('password', '').strip() + + if not username or not first_name or not last_name: + return jsonify({'success': False, 'message': 'Username, first name, and last name are required'}) + + # Check if username is taken by another user + if username != user['username']: + existing = query_db('SELECT user_id FROM Users WHERE username = ? AND user_id != ?', [username, user_id], one=True) + if existing: + return jsonify({'success': False, 'message': 'Username already taken'}) + + # Admins can't change role to admin or owner + if session['role'] == 'admin' and role in ['admin', 'owner']: + return jsonify({'success': False, 'message': 'Permission denied: Cannot assign Admin or Owner role'}) + + full_name = f"{first_name} {last_name}" + + try: + if password: + # Update with new password + hashed_password = generate_password_hash(password) + execute_db(''' + UPDATE Users + SET username = ?, full_name = ?, email = ?, role = ?, branch = ?, is_active = ?, password = ? + WHERE user_id = ? + ''', [username, full_name, email, role, branch, is_active, hashed_password, user_id]) + else: + # Update without changing password + execute_db(''' + UPDATE Users + SET username = ?, full_name = ?, email = ?, role = ?, branch = ?, is_active = ? + WHERE user_id = ? + ''', [username, full_name, email, role, branch, is_active, user_id]) + + return jsonify({'success': True, 'message': 'User updated successfully'}) + except Exception as e: + return jsonify({'success': False, 'message': f'Error updating user: {str(e)}'}) + + +@users_bp.route('/settings/users//delete', methods=['POST']) +@role_required('owner', 'admin') +def delete_user(user_id): + """Delete (deactivate) a user""" + # Get user + user = query_db('SELECT * FROM Users WHERE user_id = ?', [user_id], one=True) + + if not user: + return jsonify({'success': False, 'message': 'User not found'}) + + # Admins can't delete other admins or owners + if session['role'] == 'admin' and user['role'] in ['admin', 'owner']: + return jsonify({'success': False, 'message': 'Permission denied'}) + + # Can't delete yourself + if user_id == session['user_id']: + return jsonify({'success': False, 'message': 'Cannot delete yourself'}) + + # Soft delete (deactivate) + try: + execute_db('UPDATE Users SET is_active = 0 WHERE user_id = ?', [user_id]) + return jsonify({'success': True, 'message': 'User deleted successfully'}) + except Exception as e: + return jsonify({'success': False, 'message': f'Error deleting user: {str(e)}'}) \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 36af395..975de22 100644 --- a/templates/base.html +++ b/templates/base.html @@ -25,7 +25,7 @@
diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..6f7138b --- /dev/null +++ b/utils.py @@ -0,0 +1,31 @@ +from functools import wraps +from flask import session, flash, redirect, url_for +from db import query_db + +def login_required(f): + """Require login for route""" + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + flash('Please log in to access this page', 'warning') + return redirect(url_for('login')) + return f(*args, **kwargs) + return decorated_function + +def role_required(*roles): + """Require specific role(s) for route""" + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + flash('Please log in to access this page', 'warning') + return redirect(url_for('login')) + + user = query_db('SELECT role FROM Users WHERE user_id = ?', [session['user_id']], one=True) + if not user or user['role'] not in roles: + flash('You do not have permission to access this page', 'danger') + return redirect(url_for('dashboard')) + + return f(*args, **kwargs) + return decorated_function + return decorator \ No newline at end of file