From fcaa15bf6e20ac499f9d5796ca5f3a295bc9a241 Mon Sep 17 00:00:00 2001
From: Jason <17367223+jsoltys@users.noreply.github.com>
Date: Fri, 24 Apr 2026 14:09:04 -0500
Subject: [PATCH] Pre-github
---
.gitignore | 9 +
app/.htaccess | 7 -
app/.htaccess.minimal | 4 -
{product_app => app}/README.md | 0
app/admin/README.md | 98 --
app/admin/fix_default_locations.py | 65 --
app/admin/test_password_security.py | 60 -
app/app.py | 997 +---------------
{product_app => app}/blueprints/__init__.py | 0
{product_app => app}/blueprints/auth.py | 0
app/blueprints/products.py | 38 +
{product_app => app}/blueprints/users.py | 5 +-
.../05385681982b54969ad4a32750bdaa8e.png | Bin 150303 -> 0 bytes
app/config.py | 44 -
app/data/bitwise_legend.json | 40 -
app/data/door_accessories_data.csv | 15 -
app/data/example_user_structure.json | 68 --
app/data/image_configs.json | 94 --
app/data/product_bitwise.csv | 922 ---------------
app/data/product_code_mapping.json | 22 -
app/data/products-old.csv | 1000 -----------------
app/data/products.csv | 998 ----------------
app/data/users.json | 73 +-
app/debug_startup.py | 111 --
app/fix_passenger_file.py | 44 -
app/fix_passenger_lock.sh | 64 --
app/generate_bitwise_helper.py | 295 -----
app/image_generator.py | 297 -----
app/images/cobrai-black-black.png | Bin 488527 -> 0 bytes
app/images/cobrai-bronze-bronze.png | Bin 981947 -> 0 bytes
app/images/cobrai-sandstone-bronze.png | Bin 992688 -> 0 bytes
app/images/cobrai-white-white.png | Bin 943890 -> 0 bytes
app/images/storm-door-background.png | Bin 7091557 -> 0 bytes
app/js/encoding_helper.js | 451 --------
app/parse_csv_to_json.py | 652 -----------
app/passenger_wsgi.py | 54 +-
app/region_helper.py | 189 ----
app/requirements.txt | 5 +-
app/{ => static}/css/styles.css | 21 +-
app/{ => static}/js/script.js | 11 +-
app/templates/404.html | 77 --
app/templates/access_denied.html | 133 ---
app/templates/image_test.html | 381 -------
app/templates/index.html | 230 ----
app/templates/index2.html | 75 --
app/templates/login.html | 56 +-
app/templates/product_finder.html | 84 ++
app/templates/select_location.html | 386 ++++---
app/templates/user_manager.html | 992 +++++-----------
app/test_app.py | 228 ----
app/test_deployment.py | 171 ---
app/test_routes.py | 86 --
app/troubleshoot_server.py | 142 ---
app/wsgi.py | 8 -
app_simple/README.md | 42 -
app_simple/app.py | 37 -
app_simple/check_deployment.py | 62 -
app_simple/requirements.txt | 2 -
app_simple/templates/about.html | 62 -
app_simple/templates/home.html | 54 -
app_simple/wsgi.py | 4 -
product_app/app.py | 87 --
product_app/data/users.json | 25 -
product_app/passenger_wsgi.py | 17 -
product_app/requirements.txt | 1 -
product_app/templates/login.html | 213 ----
product_app/templates/select_location.html | 375 -------
product_app/templates/user_manager.html | 498 --------
68 files changed, 707 insertions(+), 10574 deletions(-)
delete mode 100644 app/.htaccess
delete mode 100644 app/.htaccess.minimal
rename {product_app => app}/README.md (100%)
delete mode 100644 app/admin/README.md
delete mode 100644 app/admin/fix_default_locations.py
delete mode 100644 app/admin/test_password_security.py
rename {product_app => app}/blueprints/__init__.py (100%)
rename {product_app => app}/blueprints/auth.py (100%)
create mode 100644 app/blueprints/products.py
rename {product_app => app}/blueprints/users.py (95%)
delete mode 100644 app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png
delete mode 100644 app/config.py
delete mode 100644 app/data/bitwise_legend.json
delete mode 100644 app/data/door_accessories_data.csv
delete mode 100644 app/data/example_user_structure.json
delete mode 100644 app/data/image_configs.json
delete mode 100644 app/data/product_bitwise.csv
delete mode 100644 app/data/product_code_mapping.json
delete mode 100644 app/data/products-old.csv
delete mode 100644 app/data/products.csv
delete mode 100644 app/debug_startup.py
delete mode 100644 app/fix_passenger_file.py
delete mode 100644 app/fix_passenger_lock.sh
delete mode 100644 app/generate_bitwise_helper.py
delete mode 100644 app/image_generator.py
delete mode 100644 app/images/cobrai-black-black.png
delete mode 100644 app/images/cobrai-bronze-bronze.png
delete mode 100644 app/images/cobrai-sandstone-bronze.png
delete mode 100644 app/images/cobrai-white-white.png
delete mode 100644 app/images/storm-door-background.png
delete mode 100644 app/js/encoding_helper.js
delete mode 100644 app/parse_csv_to_json.py
delete mode 100644 app/region_helper.py
rename app/{ => static}/css/styles.css (92%)
rename app/{ => static}/js/script.js (96%)
delete mode 100644 app/templates/404.html
delete mode 100644 app/templates/access_denied.html
delete mode 100644 app/templates/image_test.html
delete mode 100644 app/templates/index.html
delete mode 100644 app/templates/index2.html
create mode 100644 app/templates/product_finder.html
delete mode 100644 app/test_app.py
delete mode 100644 app/test_deployment.py
delete mode 100644 app/test_routes.py
delete mode 100644 app/troubleshoot_server.py
delete mode 100644 app/wsgi.py
delete mode 100644 app_simple/README.md
delete mode 100644 app_simple/app.py
delete mode 100644 app_simple/check_deployment.py
delete mode 100644 app_simple/requirements.txt
delete mode 100644 app_simple/templates/about.html
delete mode 100644 app_simple/templates/home.html
delete mode 100644 app_simple/wsgi.py
delete mode 100644 product_app/app.py
delete mode 100644 product_app/data/users.json
delete mode 100644 product_app/passenger_wsgi.py
delete mode 100644 product_app/requirements.txt
delete mode 100644 product_app/templates/login.html
delete mode 100644 product_app/templates/select_location.html
delete mode 100644 product_app/templates/user_manager.html
diff --git a/.gitignore b/.gitignore
index 45793a2..62ea1cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,12 @@ htmlcov/
# Production
*.pid
*.sock
+
+# Misc
+information/deprecated/
+planning/
+*.7z
+*.zip
+.htaccess_fixed
+teacher_retention_trend.svg
+wordpress_.htaccess
\ No newline at end of file
diff --git a/app/.htaccess b/app/.htaccess
deleted file mode 100644
index b8f9850..0000000
--- a/app/.htaccess
+++ /dev/null
@@ -1,7 +0,0 @@
-# Apache configuration for Flask subdirectory deployment
-# Passenger configuration ONLY - minimal to avoid blocking issues
-
-PassengerEnabled on
-PassengerAppRoot /home/bmdwtjuw/product-finder
-PassengerPython /home/bmdwtjuw/virtualenv/product-finder/3.13/bin/python3.13_bin
-SetEnv APPLICATION_ROOT /product-finder
diff --git a/app/.htaccess.minimal b/app/.htaccess.minimal
deleted file mode 100644
index 99eb75e..0000000
--- a/app/.htaccess.minimal
+++ /dev/null
@@ -1,4 +0,0 @@
-# MINIMAL .htaccess for troubleshooting
-# If you're getting 403/404, try this simplified version first
-
-PassengerEnabled on
diff --git a/product_app/README.md b/app/README.md
similarity index 100%
rename from product_app/README.md
rename to app/README.md
diff --git a/app/admin/README.md b/app/admin/README.md
deleted file mode 100644
index 7a614e7..0000000
--- a/app/admin/README.md
+++ /dev/null
@@ -1,98 +0,0 @@
-# Admin Utilities
-
-This folder contains administrative tools and utilities for maintaining the CGW Product Finder application.
-
-## š ļø Available Tools
-
-### fix_default_locations.py
-**Purpose**: Ensures all users have their default location marked as accessible.
-
-**Usage**:
-```bash
-cd app/admin
-python fix_default_locations.py
-```
-
-**What it does**:
-- Scans all users in `data/users.json`
-- Checks if each user's default location is marked as accessible
-- Automatically fixes any users where the default location is not accessible
-- Reports results for each user
-
-**When to use**:
-- After manual edits to users.json
-- After importing users from backup
-- If users report they can't access their default location
-- As a maintenance check
-
-### test_password_security.py
-**Purpose**: Demonstrates and tests the password hashing security implementation.
-
-**Usage**:
-```bash
-cd app/admin
-python test_password_security.py
-```
-
-**What it does**:
-- Demonstrates PBKDF2-SHA256 password hashing
-- Shows security features (iterations, salt, etc.)
-- Verifies password verification works correctly
-- Useful for understanding the security implementation
-
-**When to use**:
-- To verify password hashing is working correctly
-- To demonstrate security to stakeholders
-- For educational purposes
-- When troubleshooting password-related issues
-
-## š Running Admin Tools
-
-All admin tools should be run from within the `app/admin` directory to ensure correct file paths.
-
-**Example**:
-```bash
-# Navigate to admin folder
-cd "C:\Users\Work\Desktop\CGW Product Finder\app\admin"
-
-# Run a tool
-python fix_default_locations.py
-```
-
-## ā ļø Important Notes
-
-- **Backup First**: Always backup `data/users.json` before running utilities that modify data
-- **Test Environment**: Test utilities in a development environment before running in production
-- **File Paths**: These tools assume they're being run from the `app/admin` directory
-- **Python Version**: Requires Python 3.7 or higher
-
-## š Security Considerations
-
-- These tools have direct access to user data
-- Do not expose these tools to web-facing directories
-- Keep this folder secure with appropriate file permissions
-- Never commit sensitive user data to version control
-
-## š Adding New Admin Tools
-
-When adding new administrative tools to this folder:
-
-1. Add the Python script file
-2. Update this README with documentation
-3. Include clear usage instructions
-4. Document any data modifications it makes
-5. Include error handling and user feedback
-
-## š Troubleshooting
-
-**"File not found" errors**:
-- Ensure you're running from the `app/admin` directory
-- Check that `../data/users.json` exists
-
-**Permission denied**:
-- Ensure the application is not running
-- Check file permissions on `data/users.json`
-
-**Import errors**:
-- Ensure all required dependencies are installed: `pip install -r ../requirements.txt`
-- Verify Python version: `python --version`
diff --git a/app/admin/fix_default_locations.py b/app/admin/fix_default_locations.py
deleted file mode 100644
index 2b2f982..0000000
--- a/app/admin/fix_default_locations.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
-Utility script to fix existing users by ensuring their default location
-is marked as accessible in their locationSettings.
-"""
-
-import json
-import os
-
-# Get the directory where this script is located
-basedir = os.path.abspath(os.path.dirname(__file__))
-USERS_FILE = os.path.join(basedir, '..', 'data', 'users.json')
-
-def fix_default_locations():
- """
- Ensure all users have their default location marked as accessible.
- """
- if not os.path.exists(USERS_FILE):
- print("No users.json file found.")
- return
-
- # Load users
- with open(USERS_FILE, 'r') as f:
- users = json.load(f)
-
- print(f"Found {len(users)} users to check...")
-
- changes_made = 0
- for user in users:
- username = user.get('username', 'Unknown')
- default_location = user.get('defaultLocation')
-
- if not default_location:
- print(f"ā ļø User '{username}' has no default location - skipping")
- continue
-
- location_settings = user.get('locationSettings', {})
-
- # Check if default location is in settings and accessible
- if default_location not in location_settings:
- print(f"ā Adding default location '{default_location}' for user '{username}'")
- location_settings[default_location] = {'accessible': True}
- user['locationSettings'] = location_settings
- changes_made += 1
- elif not location_settings[default_location].get('accessible', False):
- print(f"ā Marking default location '{default_location}' as accessible for user '{username}'")
- location_settings[default_location]['accessible'] = True
- changes_made += 1
- else:
- print(f" User '{username}' - default location already accessible")
-
- if changes_made > 0:
- # Save updated users
- with open(USERS_FILE, 'w') as f:
- json.dump(users, f, indent=2)
- print(f"\nā Fixed {changes_made} user(s) and saved to users.json")
- else:
- print("\nā All users already have correct default location settings")
-
-if __name__ == '__main__':
- print("=" * 60)
- print("DEFAULT LOCATION FIX UTILITY")
- print("=" * 60)
- print()
- fix_default_locations()
- print()
diff --git a/app/admin/test_password_security.py b/app/admin/test_password_security.py
deleted file mode 100644
index b2ce388..0000000
--- a/app/admin/test_password_security.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-Test script to demonstrate password hashing and verification
-"""
-
-from werkzeug.security import generate_password_hash, check_password_hash
-
-# Example: Creating a hashed password
-plain_password = "mySecurePassword123"
-hashed_password = generate_password_hash(plain_password, method='pbkdf2:sha256')
-
-print("=" * 60)
-print("PASSWORD HASHING DEMONSTRATION")
-print("=" * 60)
-print()
-print(f"Original Password: {plain_password}")
-print()
-print(f"Hashed Password: {hashed_password}")
-print()
-print(f"Hash Length: {len(hashed_password)} characters")
-print()
-print("=" * 60)
-print("SECURITY FEATURES")
-print("=" * 60)
-print("ā Algorithm: PBKDF2-SHA256")
-print("ā Iterations: 600,000+")
-print("ā Unique salt per password")
-print("ā Cannot be reversed to plain text")
-print("ā Industry-standard security")
-print()
-print("=" * 60)
-print("PASSWORD VERIFICATION TEST")
-print("=" * 60)
-print()
-
-# Test correct password
-correct = check_password_hash(hashed_password, "mySecurePassword123")
-print(f"Testing correct password: {correct} ā")
-
-# Test incorrect password
-incorrect = check_password_hash(hashed_password, "wrongPassword")
-print(f"Testing incorrect password: {incorrect} ā")
-print()
-
-# Show that the same password produces different hashes (due to unique salts)
-print("=" * 60)
-print("UNIQUE SALT DEMONSTRATION")
-print("=" * 60)
-print()
-print("Hashing the same password twice produces different hashes:")
-print()
-hash1 = generate_password_hash("password123", method='pbkdf2:sha256')
-hash2 = generate_password_hash("password123", method='pbkdf2:sha256')
-print(f"Hash 1: {hash1}")
-print(f"Hash 2: {hash2}")
-print()
-print(f"Are they different? {hash1 != hash2}")
-print("Both hashes are valid and will verify correctly!")
-print(f"Hash 1 verifies: {check_password_hash(hash1, 'password123')}")
-print(f"Hash 2 verifies: {check_password_hash(hash2, 'password123')}")
-print()
diff --git a/app/app.py b/app/app.py
index 57b4149..64efd40 100644
--- a/app/app.py
+++ b/app/app.py
@@ -1,25 +1,6 @@
-from flask import Flask, render_template, send_from_directory, jsonify, request, send_file, session, redirect, url_for
-import os
-from io import BytesIO
-import base64
-import json
-from werkzeug.security import generate_password_hash, check_password_hash
-from functools import wraps
+from flask import Flask, session, redirect, url_for
import secrets
-
-# Import image generator
-try:
- from image_generator import ProductImageGenerator, image_to_base64
- IMAGE_GENERATOR_AVAILABLE = True
- print("ā Image generation available")
-except ImportError as e:
- IMAGE_GENERATOR_AVAILABLE = False
- print(f"Warning: Could not import image generator: {e}")
- print("Image generation endpoints will be disabled.")
-except Exception as e:
- IMAGE_GENERATOR_AVAILABLE = False
- print(f"Error loading image generator: {e}")
- print("Image generation endpoints will be disabled.")
+import os
# Get the directory where this script is located
basedir = os.path.abspath(os.path.dirname(__file__))
@@ -34,962 +15,76 @@ app.secret_key = secrets.token_hex(32)
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
-# Configure static folders
-app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable caching during development
+# Import and register blueprints
+from blueprints.auth import auth_bp
+from blueprints.users import users_bp
+from blueprints.products import products_bp
-# Support for subdirectory deployments (e.g., /product-finder/)
-# Auto-detect production environment based on Python path
-import sys
-if 'virtualenv/product-finder' in sys.executable or '/home/bmdwtjuw' in sys.executable:
- # Running on production server
- app.config['APPLICATION_ROOT'] = '/product-finder'
- print("[OK] Production environment detected - using /product-finder")
-else:
- # Local development
- app.config['APPLICATION_ROOT'] = os.environ.get('APPLICATION_ROOT', '/')
- print(f"[OK] Development environment - using {app.config['APPLICATION_ROOT']}")
-
-# ===== SUBDIRECTORY DEPLOYMENT MIDDLEWARE =====
-
-class PrefixMiddleware:
- """
- Middleware to handle subdirectory deployments.
- This ensures Flask knows about the /product-finder prefix when deployed.
- """
- def __init__(self, app, prefix=''):
- self.app = app
- self.prefix = prefix.rstrip('/') # Remove trailing slash if present
-
- def __call__(self, environ, start_response):
- # Only apply prefix if not already in SCRIPT_NAME and prefix is set
- if self.prefix and self.prefix != '/':
- # Check if the URL path starts with our prefix
- path = environ.get('PATH_INFO', '')
- script_name = environ.get('SCRIPT_NAME', '')
-
- # If prefix not already in SCRIPT_NAME, add it
- if not script_name.startswith(self.prefix):
- environ['SCRIPT_NAME'] = self.prefix + script_name
-
- # Remove prefix from PATH_INFO if it's there
- if path.startswith(self.prefix):
- environ['PATH_INFO'] = path[len(self.prefix):]
-
- return self.app(environ, start_response)
-
-# Apply middleware if APPLICATION_ROOT is set to a subdirectory
-application_root = app.config.get('APPLICATION_ROOT', '/')
-if application_root and application_root != '/':
- app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=application_root)
- print(f"ā PrefixMiddleware applied for subdirectory: {application_root}")
-
-# Make base URL available to all templates
-@app.context_processor
-def inject_base_url():
- """Inject base URL into all templates for relative paths"""
- return {
- 'base_url': request.script_root or '',
- 'url_for': url_for
- }
-
-# ===== AUTHENTICATION HELPERS =====
-
-def login_required(f):
- """Decorator to require login for routes"""
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if 'user_id' not in session:
- return redirect(url_for('login_page'))
-
- # Check if user is active
- users = load_users()
- if session['user_id'] >= len(users):
- session.clear()
- return redirect(url_for('login_page', message='User not found'))
-
- user = users[session['user_id']]
- if not user.get('active', True):
- session.clear()
- return redirect(url_for('login_page', message='Account is inactive'))
-
- return f(*args, **kwargs)
- return decorated_function
-
-def get_current_user():
- """Get the currently logged in user"""
- if 'user_id' not in session:
- return None
-
- users = load_users()
- if session['user_id'] >= len(users):
- return None
-
- return users[session['user_id']]
-
-def can_user(permission, location=None):
- """
- Check if current user has a specific permission.
- WordPress-style permission checking.
-
- Args:
- permission (str): Permission name (e.g., 'manage_users', 'create_quotes')
- location (str, optional): Check permission for specific location.
- If None, uses current session location.
- If 'global', checks global permissions only.
-
- Returns:
- bool: True if user has permission, False otherwise
-
- Examples:
- can_user('manage_users') # Check global permission
- can_user('create_quotes') # Check at current location
- can_user('view_reports', 'LINDS') # Check at specific location
- """
- user = get_current_user()
- if not user:
- return False
-
- # Check if user is active
- if not user.get('active', True):
- return False
-
- # Check global permissions first
- global_permissions = user.get('permissions', {})
- if permission in global_permissions:
- return global_permissions[permission] is True
-
- # If location is 'global', only check global permissions
- if location == 'global':
- return False
-
- # Determine which location to check
- check_location = location if location else session.get('currentLocation')
-
- if not check_location:
- return False
-
- # Check location-specific permissions
- location_settings = user.get('locationSettings', {})
- if check_location in location_settings:
- loc_permissions = location_settings[check_location].get('permissions', {})
- if permission in loc_permissions:
- return loc_permissions[permission] is True
-
- return False
-
-def user_has_any_permission(permissions, location=None):
- """
- Check if user has ANY of the provided permissions.
-
- Args:
- permissions (list): List of permission names
- location (str, optional): Location to check
-
- Returns:
- bool: True if user has at least one permission
- """
- return any(can_user(perm, location) for perm in permissions)
-
-def user_has_all_permissions(permissions, location=None):
- """
- Check if user has ALL of the provided permissions.
-
- Args:
- permissions (list): List of permission names
- location (str, optional): Location to check
-
- Returns:
- bool: True if user has all permissions
- """
- return all(can_user(perm, location) for perm in permissions)
-
-def permission_required(permission, location=None):
- """
- Decorator to require specific permission for a route.
- Similar to @login_required but checks permissions.
-
- Args:
- permission (str): Required permission name
- location (str, optional): Location to check permission for
-
- Example:
- @app.route('/admin/users')
- @login_required
- @permission_required('manage_users')
- def manage_users_page():
- return render_template('user_manager.html')
- """
- def decorator(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not can_user(permission, location):
- return render_template('access_denied.html',
- required_permission=permission), 403
- return f(*args, **kwargs)
- return decorated_function
- return decorator
-
-def get_user_permissions(location=None):
- """
- Get all permissions for the current user.
-
- Args:
- location (str, optional): Get permissions for specific location.
- If None, returns global + current location.
-
- Returns:
- dict: Dictionary of permission_name: True/False
- """
- user = get_current_user()
- if not user:
- return {}
-
- permissions = {}
-
- # Get global permissions
- global_perms = user.get('permissions', {})
- permissions.update(global_perms)
-
- # Get location-specific permissions
- if location != 'global':
- check_location = location if location else session.get('currentLocation')
- if check_location:
- location_settings = user.get('locationSettings', {})
- if check_location in location_settings:
- loc_perms = location_settings[check_location].get('permissions', {})
- permissions.update(loc_perms)
-
- return permissions
-
-# ===== USER MANAGEMENT FUNCTIONS (moved here for use in auth) =====
-
-USERS_FILE = os.path.join(basedir, '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"""
- with open(USERS_FILE, 'w') as f:
- json.dump(users, f, indent=2)
-
-# ===== AUTHENTICATION ROUTES =====
-
-@app.route('/login')
-def login_page():
- """Serve the login page"""
- # If already logged in, redirect to main app
- if 'user_id' in session:
- return redirect(url_for('index'))
- return render_template('login.html')
-
-@app.route('/select-location')
-def select_location_page():
- """Serve the location selection page"""
- if 'user_id' not in session:
- return redirect(url_for('login_page'))
- return render_template('select_location.html')
-
-@app.route('/api/login', methods=['POST'])
-def login():
- """Authenticate user and create session"""
- try:
- data = request.json
- username = data.get('username')
- password = data.get('password')
-
- if not username or not password:
- return jsonify({
- 'status': 'error',
- '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
-
- if not user:
- return jsonify({
- 'status': 'error',
- 'message': 'Invalid username or password'
- }), 401
-
- # Check if user is active
- if not user.get('active', True):
- return jsonify({
- 'status': 'error',
- 'message': 'Account is inactive. Please contact an administrator.'
- }), 403
-
- # Verify password
- if not check_password_hash(user['password'], password):
- return jsonify({
- 'status': 'error',
- 'message': 'Invalid username or password'
- }), 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():
- if settings.get('accessible', False):
- accessible_locations.append(loc_code)
-
- session['accessibleLocations'] = accessible_locations
-
- # Check if user needs to select a location
- requiresLocationSelection = len(accessible_locations) > 1
-
- # If only one location or no accessible locations, auto-select default
- if not requiresLocationSelection:
- session['currentLocation'] = user.get('defaultLocation')
-
- return jsonify({
- 'status': 'success',
- 'message': 'Login successful',
- 'requiresLocationSelection': requiresLocationSelection
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/logout', methods=['POST'])
-def logout():
- """Log out user and clear session"""
- session.clear()
- return jsonify({
- 'status': 'success',
- 'message': 'Logged out successfully'
- })
-
-@app.route('/api/session', methods=['GET'])
-def get_session():
- """Get current session information"""
- if 'user_id' not in session:
- return jsonify({
- 'status': 'error',
- 'message': 'Not logged in'
- }), 401
-
- return jsonify({
- 'status': 'success',
- 'username': session.get('username'),
- 'defaultLocation': session.get('defaultLocation'),
- 'currentLocation': session.get('currentLocation'),
- 'accessibleLocations': session.get('accessibleLocations', [])
- })
-
-@app.route('/api/select-location', methods=['POST'])
-def select_location():
- """Select a location for the current session"""
- if 'user_id' not in session:
- return jsonify({
- 'status': 'error',
- 'message': 'Not logged in'
- }), 401
-
- try:
- data = request.json
- location = data.get('location')
-
- # Verify user has access to this location
- accessible = session.get('accessibleLocations', [])
- if location not in accessible:
- return jsonify({
- 'status': 'error',
- 'message': 'You do not have access to this location'
- }), 403
-
- session['currentLocation'] = location
-
- return jsonify({
- 'status': 'success',
- 'message': 'Location selected',
- 'currentLocation': location
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-# ===== PERMISSION CHECK ENDPOINT =====
-
-@app.route('/api/check-permission', methods=['POST'])
-@login_required
-def check_permission():
- """Check if current user has specific permission(s)"""
- try:
- data = request.json
- permission = data.get('permission')
- location = data.get('location') # Optional, defaults to current location
-
- if not permission:
- return jsonify({
- 'status': 'error',
- 'message': 'Permission name required'
- }), 400
-
- has_permission = can_user(permission, location)
-
- return jsonify({
- 'status': 'success',
- 'hasPermission': has_permission,
- 'permission': permission,
- 'location': location if location else session.get('currentLocation', 'global')
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/user-permissions', methods=['GET'])
-@login_required
-def get_user_permissions_endpoint():
- """Get all permissions for current user"""
- try:
- location = request.args.get('location') # Optional query parameter
- permissions = get_user_permissions(location)
-
- return jsonify({
- 'status': 'success',
- 'permissions': permissions,
- 'location': location if location else session.get('currentLocation', 'global')
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-# ===== MAIN APPLICATION ROUTES =====
+app.register_blueprint(auth_bp)
+app.register_blueprint(users_bp)
+app.register_blueprint(products_bp)
+# Home route
@app.route('/')
-def index():
- """Serve the quiz page as the main page or redirect to login"""
- # Simple test to see if app is running
- if 'username' not in session:
- return redirect(url_for('login_page'))
+def home():
+ """Redirect to login if not authenticated, otherwise show home"""
+ if 'user_id' not in session:
+ return redirect(url_for('auth.login_page'))
# If user hasn't selected a location yet, redirect to selection
if not session.get('currentLocation') and len(session.get('accessibleLocations', [])) > 1:
- return redirect(url_for('select_location_page'))
- return render_template('index2.html')
-
-@app.route('/test')
-def test_route():
- """Simple test route to verify app is running - NO AUTH REQUIRED"""
- import sys
- info = {
- 'status': 'OK',
- 'message': 'Flask app is running!',
- 'python': sys.version,
- 'application_root': app.config.get('APPLICATION_ROOT'),
- 'middleware': type(app.wsgi_app).__name__,
- 'routes': len(list(app.url_map.iter_rules()))
- }
+ return redirect(url_for('auth.select_location_page'))
+
return f"""
-
App Test
-
- ā Flask App is Running!
+ Home
+
+ Welcome, {session.get('username', 'User')}!
+ Current Location: {session.get('currentLocation', 'Not selected')}
- Go to Login
"""
-@app.route('/quiz')
-@login_required
-def quiz():
- """Serve the quiz page"""
- return render_template('index2.html')
-
-@app.route('/image-test')
-@login_required
-def image_test():
- """Serve the image generation test page"""
- return render_template('image_test.html')
-
-@app.route('/css/')
-def serve_css(filename):
- """Serve CSS files"""
- return send_from_directory(os.path.join(basedir, 'css'), filename)
-
-@app.route('/js/')
-def serve_js(filename):
- """Serve JavaScript files"""
- return send_from_directory(os.path.join(basedir, 'js'), filename)
-
-@app.route('/images/')
-def serve_images(filename):
- """Serve image files"""
- return send_from_directory(os.path.join(basedir, 'images'), filename)
-
-@app.route('/data/')
-def serve_data(filename):
- """Serve data files (JSON)"""
- return send_from_directory(os.path.join(basedir, 'data'), filename)
-
-# API endpoint for future expansion (e.g., saving user selections)
-@app.route('/api/save-selection', methods=['POST'])
-def save_selection():
- """API endpoint to save user selections"""
- data = request.json
- # Here you could save to a database or file
- # For now, just return success
- return jsonify({
- 'status': 'success',
- 'message': 'Selection saved',
- 'data': data
- })
-
-@app.route('/api/get-products', methods=['GET'])
-def get_products():
- """API endpoint to get product data"""
- # This could fetch from a database in the future
- return jsonify({
- 'status': 'success',
- 'products': []
- })
-
-# ===== DYNAMIC IMAGE GENERATION ENDPOINTS =====
-
-@app.route('/api/product-image/')
-def generate_product_image(product_code):
+@app.route('/test')
+def test():
+ """Test route to verify app is running"""
+ import sys
+ return f"""
+
+ Test Page
+
+ Flask App is Running!
+
+ - Python: {sys.version}
+ - Routes: {len(list(app.url_map.iter_rules()))}
+
+ Login
+
+
"""
- Generate a product image with specified configuration.
-
- URL Parameters:
- color: Color name (default: 'white')
- hinge: Hinge side - 'left' or 'right' (default: 'right')
- material: Material type (default: 'aluminum')
- format: Return format - 'image' or 'json' (default: 'image')
-
- Examples:
- /api/product-image/cobrai?color=black&hinge=left
- /api/product-image/cobrai?color=bronze&hinge=right&format=json
- """
- if not IMAGE_GENERATOR_AVAILABLE:
- return jsonify({
- 'error': 'Image generation not available. Please install Pillow: pip install Pillow'
- }), 500
-
- try:
- # Get parameters
- color = request.args.get('color', 'white').lower()
- hinge = request.args.get('hinge', 'right').lower()
- material = request.args.get('material', 'aluminum').lower()
- return_format = request.args.get('format', 'image').lower()
- use_cache = request.args.get('cache', 'true').lower() == 'true'
-
- # Initialize generator
- generator = ProductImageGenerator()
-
- # Generate image
- image = generator.generate_product_image(
- product_code=product_code,
- color=color,
- hinge=hinge,
- material=material,
- use_cache=use_cache
- )
-
- # Return based on format
- if return_format == 'json':
- # Return base64 encoded image in JSON
- img_base64 = image_to_base64(image, format='PNG')
- return jsonify({
- 'status': 'success',
- 'productCode': product_code,
- 'color': color,
- 'hinge': hinge,
- 'material': material,
- 'image': img_base64,
- 'format': 'base64'
- })
- else:
- # Return raw image file
- img_io = BytesIO()
- image.save(img_io, 'PNG', optimize=True)
- img_io.seek(0)
- return send_file(img_io, mimetype='image/png')
-
- except ValueError as e:
- return jsonify({'error': str(e)}), 404
- except FileNotFoundError as e:
- return jsonify({'error': str(e)}), 404
- except Exception as e:
- return jsonify({'error': f'Error generating image: {str(e)}'}), 500
-
-@app.route('/api/product-config/')
-def get_product_image_config(product_code):
- """
- Get the image configuration for a product.
-
- Returns JSON with available colors, hardware positions, and other metadata.
-
- Example:
- /api/product-config/cobrai
- """
- if not IMAGE_GENERATOR_AVAILABLE:
- return jsonify({
- 'error': 'Image generation not available. Please install Pillow: pip install Pillow'
- }), 500
-
- try:
- generator = ProductImageGenerator()
- config = generator.get_product_config(product_code)
-
- if not config:
- return jsonify({
- 'error': f'Configuration for product {product_code} not found'
- }), 404
-
- return jsonify({
- 'status': 'success',
- 'productCode': product_code,
- 'config': config
- })
-
- except Exception as e:
- return jsonify({'error': str(e)}), 500
-
-@app.route('/api/clear-image-cache', methods=['POST'])
-def clear_image_cache():
- """
- Clear the image cache.
-
- Optional JSON body:
- { "productCode": "cobrai" } - Clear cache for specific product
-
- Example:
- POST /api/clear-image-cache
- POST /api/clear-image-cache with body: {"productCode": "cobrai"}
- """
- if not IMAGE_GENERATOR_AVAILABLE:
- return jsonify({
- 'error': 'Image generation not available.'
- }), 500
-
- try:
- data = request.json or {}
- product_code = data.get('productCode')
-
- generator = ProductImageGenerator()
- generator.clear_cache(product_code)
-
- return jsonify({
- 'status': 'success',
- 'message': f'Cache cleared for {product_code}' if product_code else 'All cache cleared'
- })
-
- except Exception as e:
- return jsonify({'error': str(e)}), 500
-
-# ===== END DYNAMIC IMAGE GENERATION ENDPOINTS =====
-
-# ===== USER MANAGEMENT ENDPOINTS =====
-
-@app.route('/users')
-@login_required
-@permission_required('manage_users')
-def user_manager():
- """Serve the user management page"""
- return render_template('user_manager.html')
-
-@app.route('/api/users', methods=['GET'])
-@login_required
-@permission_required('manage_users')
-def get_users():
- """Get all users"""
- users = load_users()
- return jsonify({
- 'status': 'success',
- 'users': users
- })
-
-@app.route('/api/users', methods=['POST'])
-@login_required
-@permission_required('manage_users')
-def add_user():
- """Add a new user with hashed password"""
- try:
- data = request.json
-
- # Validate required fields
- if not data.get('username') or not data.get('password') or not data.get('defaultLocation'):
- return jsonify({
- 'status': 'error',
- 'message': 'Username, password, and default location are required'
- }), 400
-
- users = load_users()
-
- # Check if username already exists
- if any(user['username'] == data['username'] for user in users):
- return jsonify({
- 'status': 'error',
- 'message': 'Username already exists'
- }), 400
-
- # Hash the password using pbkdf2:sha256 (secure)
- hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256')
-
- # Get location settings and ensure default location is marked as accessible
- location_settings = data.get('locationSettings', {})
- default_location = data['defaultLocation']
-
- # Ensure default location exists in settings and is marked as 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,
- 'active': data.get('active', True) # Default to active
- }
-
- users.append(new_user)
- save_users(users)
-
- return jsonify({
- 'status': 'success',
- 'message': 'User added successfully',
- 'users': users
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/users//active', methods=['PATCH'])
-@login_required
-@permission_required('manage_users')
-def toggle_user_active(user_index):
- """Toggle user active status"""
- try:
- data = request.json
- users = load_users()
-
- if user_index < 0 or user_index >= len(users):
- return jsonify({
- 'status': 'error',
- 'message': 'Invalid user index'
- }), 400
-
- # Update active status
- users[user_index]['active'] = data.get('active', True)
- save_users(users)
-
- return jsonify({
- 'status': 'success',
- 'message': 'User status updated',
- 'users': users
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/users/', methods=['DELETE'])
-@login_required
-@permission_required('manage_users')
-def delete_user(user_index):
- """Delete a specific user by index"""
- try:
- users = load_users()
-
- if user_index < 0 or user_index >= len(users):
- return jsonify({
- 'status': 'error',
- 'message': 'Invalid user index'
- }), 400
-
- users.pop(user_index)
- save_users(users)
-
- return jsonify({
- 'status': 'success',
- 'message': 'User deleted successfully',
- 'users': users
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/users//change-password', methods=['POST'])
-@login_required
-@permission_required('manage_users')
-def change_user_password(user_index):
- """Change a user's password with current user verification"""
- try:
- data = request.json
- new_password = data.get('newPassword')
- current_user_password = data.get('currentUserPassword')
-
- # Validate required fields
- if not new_password or not current_user_password:
- return jsonify({
- 'status': 'error',
- 'message': 'New password and current user password are required'
- }), 400
-
- # Validate password length
- if len(new_password) < 6:
- return jsonify({
- 'status': 'error',
- 'message': 'Password must be at least 6 characters long'
- }), 400
-
- # Get current logged-in user
- current_user = get_current_user()
- if not current_user:
- return jsonify({
- 'status': 'error',
- 'message': 'Not authenticated'
- }), 401
-
- # Verify current user's password
- if not check_password_hash(current_user['password'], current_user_password):
- return jsonify({
- 'status': 'error',
- '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):
- return jsonify({
- 'status': 'error',
- 'message': 'Invalid user index'
- }), 400
-
- # Hash the new password
- hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256')
-
- # Update the target user's password
- target_username = users[user_index]['username']
- users[user_index]['password'] = hashed_password
- save_users(users)
-
- return jsonify({
- 'status': 'success',
- 'message': f'Password changed successfully for user "{target_username}"'
- })
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/users/clear', methods=['DELETE'])
-def clear_all_users():
- """Clear all users"""
- try:
- save_users([])
- return jsonify({
- 'status': 'success',
- 'message': 'All users cleared'
- })
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-@app.route('/api/users/download')
-def download_users():
- """Download users as JSON file"""
- try:
- users = load_users()
-
- if not users:
- return jsonify({
- 'status': 'error',
- 'message': 'No users to download'
- }), 400
-
- # Create JSON file in memory
- json_data = json.dumps(users, indent=2)
-
- # Send as downloadable file
- return send_file(
- BytesIO(json_data.encode()),
- mimetype='application/json',
- as_attachment=True,
- download_name='users.json'
- )
-
- except Exception as e:
- return jsonify({
- 'status': 'error',
- 'message': str(e)
- }), 500
-
-# ===== END USER MANAGEMENT ENDPOINTS =====
# Error handlers
@app.errorhandler(404)
def page_not_found(e):
- return render_template('404.html'), 404
+ return "404 - Page Not Found
", 404
@app.errorhandler(500)
def internal_error(e):
- return "Internal Server Error", 500
+ return "500 - Internal Server Error
", 500
+
+# This is the critical line for Passenger
+application = app
if __name__ == '__main__':
# Create necessary directories
- os.makedirs(os.path.join(basedir, 'images'), exist_ok=True)
os.makedirs(os.path.join(basedir, 'templates'), exist_ok=True)
- os.makedirs(os.path.join(basedir, 'css'), exist_ok=True)
- os.makedirs(os.path.join(basedir, 'js'), exist_ok=True)
os.makedirs(os.path.join(basedir, 'data'), exist_ok=True)
# Run the application
- # Set debug=False for production
- # Port 8080 is used instead of 5000 (Windows reserves port 5000)
- app.run(debug=True, host='0.0.0.0', port=8080)
+ app.run(debug=True, host='0.0.0.0', port=8080)
\ No newline at end of file
diff --git a/product_app/blueprints/__init__.py b/app/blueprints/__init__.py
similarity index 100%
rename from product_app/blueprints/__init__.py
rename to app/blueprints/__init__.py
diff --git a/product_app/blueprints/auth.py b/app/blueprints/auth.py
similarity index 100%
rename from product_app/blueprints/auth.py
rename to app/blueprints/auth.py
diff --git a/app/blueprints/products.py b/app/blueprints/products.py
new file mode 100644
index 0000000..318483b
--- /dev/null
+++ b/app/blueprints/products.py
@@ -0,0 +1,38 @@
+"""
+Product Finder Blueprint
+Handles the product quiz and recommendations
+"""
+from flask import Blueprint, render_template, send_from_directory
+from blueprints.auth import login_required
+import os
+
+products_bp = Blueprint('products', __name__, url_prefix='/quiz')
+
+# Get base directory
+basedir = os.path.dirname(os.path.dirname(__file__))
+
+@products_bp.route('/')
+@login_required
+def product_finder():
+ """Serve the product finder quiz page"""
+ return render_template('product_finder.html')
+
+@products_bp.route('/css/')
+def serve_css(filename):
+ """Serve CSS files"""
+ return send_from_directory(os.path.join(basedir, 'static', 'css'), filename)
+
+@products_bp.route('/js/')
+def serve_js(filename):
+ """Serve JavaScript files"""
+ return send_from_directory(os.path.join(basedir, 'static', 'js'), filename)
+
+@products_bp.route('/images/')
+def serve_images(filename):
+ """Serve image files"""
+ return send_from_directory(os.path.join(basedir, 'static', 'images'), filename)
+
+@products_bp.route('/data/')
+def serve_data(filename):
+ """Serve data files (JSON)"""
+ return send_from_directory(os.path.join(basedir, 'data'), filename)
diff --git a/product_app/blueprints/users.py b/app/blueprints/users.py
similarity index 95%
rename from product_app/blueprints/users.py
rename to app/blueprints/users.py
index fd6b018..330e9d8 100644
--- a/product_app/blueprints/users.py
+++ b/app/blueprints/users.py
@@ -2,7 +2,7 @@
User Management Blueprint
Handles user CRUD operations
"""
-from flask import Blueprint, render_template, request, jsonify, send_file
+from flask import Blueprint, render_template, request, jsonify, send_file, session
from werkzeug.security import generate_password_hash, check_password_hash
from blueprints.auth import login_required, can_user, get_current_user
from io import BytesIO
@@ -52,7 +52,8 @@ def user_manager():
"""Serve the user management page"""
if not can_user('manage_users'):
return "403 - Permission Denied
You don't have permission to manage users.
", 403
- return render_template('user_manager.html')
+ current_user_id = session.get('user_id', -1)
+ return render_template('user_manager.html', current_user_id=current_user_id)
@users_bp.route('/api', methods=['GET'])
@login_required
diff --git a/app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png b/app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png
deleted file mode 100644
index b7735f8f27fc1213fe1c91565aaede3558a5493e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 150303
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