335 lines
9.5 KiB
Markdown
335 lines
9.5 KiB
Markdown
# /product-finder Deployment Fix Guide
|
|
|
|
## 🔴 Problem
|
|
|
|
The application works locally at `http://localhost:8080/` but fails on the server at `https://columbiawindows.com/product-finder/`.
|
|
|
|
The redirect from `/product-finder` → `/product-finder/login` works, but then the login page or subsequent routes fail.
|
|
|
|
## ✅ Solution Applied
|
|
|
|
Three critical changes were made to fix subdirectory deployment:
|
|
|
|
### 1. **Added PrefixMiddleware to app.py**
|
|
|
|
This middleware tells Flask about the `/product-finder` prefix by setting `SCRIPT_NAME` in the WSGI environment:
|
|
|
|
```python
|
|
class PrefixMiddleware:
|
|
"""Middleware to handle subdirectory deployments"""
|
|
def __init__(self, app, prefix=''):
|
|
self.app = app
|
|
self.prefix = prefix.rstrip('/')
|
|
|
|
def __call__(self, environ, start_response):
|
|
if self.prefix and self.prefix != '/':
|
|
path = environ.get('PATH_INFO', '')
|
|
script_name = environ.get('SCRIPT_NAME', '')
|
|
|
|
if not script_name.startswith(self.prefix):
|
|
environ['SCRIPT_NAME'] = self.prefix + script_name
|
|
if path.startswith(self.prefix):
|
|
environ['PATH_INFO'] = path[len(self.prefix):]
|
|
|
|
return self.app(environ, start_response)
|
|
```
|
|
|
|
This is automatically applied when `APPLICATION_ROOT` is set.
|
|
|
|
### 2. **Updated passenger_wsgi.py**
|
|
|
|
Sets the `APPLICATION_ROOT` environment variable before importing the app:
|
|
|
|
```python
|
|
if 'APPLICATION_ROOT' not in os.environ:
|
|
os.environ['APPLICATION_ROOT'] = '/product-finder'
|
|
```
|
|
|
|
### 3. **Updated config.py**
|
|
|
|
Production configuration now defaults to `/product-finder`:
|
|
|
|
```python
|
|
class ProductionConfig(Config):
|
|
APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/product-finder')
|
|
```
|
|
|
|
Development still uses `/` for local testing.
|
|
|
|
## 📤 Files to Upload
|
|
|
|
Upload these updated files to your server:
|
|
|
|
1. **`app/app.py`** - Contains PrefixMiddleware
|
|
2. **`app/passenger_wsgi.py`** - Sets APPLICATION_ROOT env var
|
|
3. **`app/config.py`** - Production defaults to /product-finder
|
|
4. **`app/.htaccess`** - Apache configuration (new file)
|
|
5. **All template files** - Already updated with url_for() and BASE_URL
|
|
|
|
## 🔧 Server Configuration
|
|
|
|
### Option A: Using .htaccess (Recommended)
|
|
|
|
The `.htaccess` file is already configured for `/product-finder`. Upload it to your `app/` folder on the server.
|
|
|
|
**Important:** Update these lines in `.htaccess`:
|
|
```apache
|
|
PassengerAppRoot /home/USERNAME/public_html/product-finder
|
|
PassengerPython /usr/bin/python3 # Update to your Python path
|
|
```
|
|
|
|
### Option B: Apache Virtual Host Configuration
|
|
|
|
If you have access to Apache config, add this to your virtual host:
|
|
|
|
```apache
|
|
<Directory "/home/USERNAME/public_html/product-finder">
|
|
SetEnv APPLICATION_ROOT /product-finder
|
|
PassengerEnabled on
|
|
PassengerAppRoot /home/USERNAME/public_html/product-finder
|
|
PassengerPython /usr/bin/python3
|
|
Allow from all
|
|
Options -MultiViews
|
|
</Directory>
|
|
```
|
|
|
|
## 🚀 Deployment Steps
|
|
|
|
1. **Backup Current Installation**
|
|
```bash
|
|
mv product-finder product-finder.backup
|
|
```
|
|
|
|
2. **Upload Updated Files**
|
|
- Upload entire `app/` folder to server
|
|
- Or just upload the 5 changed files listed above
|
|
|
|
3. **Restart Passenger**
|
|
```bash
|
|
# In product-finder folder
|
|
mkdir -p tmp
|
|
touch tmp/restart.txt
|
|
```
|
|
|
|
4. **Test the Application**
|
|
- Go to: `https://columbiawindows.com/product-finder/`
|
|
- Should redirect to: `https://columbiawindows.com/product-finder/login`
|
|
- Login page should load with all CSS/JS
|
|
- Login should work and redirect properly
|
|
- All routes should work: `/product-finder/users`, etc.
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### Issue: Still getting 404 on login page
|
|
|
|
**Check server error logs:**
|
|
```bash
|
|
tail -f ~/logs/error_log # or wherever your error logs are
|
|
```
|
|
|
|
**Verify APPLICATION_ROOT is set:**
|
|
Add this test route to app.py temporarily:
|
|
```python
|
|
@app.route('/debug-config')
|
|
def debug_config():
|
|
return jsonify({
|
|
'APPLICATION_ROOT': app.config.get('APPLICATION_ROOT'),
|
|
'script_root': request.script_root,
|
|
'url_root': request.url_root,
|
|
'base_url': request.base_url
|
|
})
|
|
```
|
|
|
|
Then visit: `https://columbiawindows.com/product-finder/debug-config`
|
|
|
|
**Expected response:**
|
|
```json
|
|
{
|
|
"APPLICATION_ROOT": "/product-finder",
|
|
"script_root": "/product-finder",
|
|
"url_root": "https://columbiawindows.com/product-finder/",
|
|
"base_url": "https://columbiawindows.com/product-finder/debug-config"
|
|
}
|
|
```
|
|
|
|
### Issue: CSS/JS files not loading
|
|
|
|
**Check that routes are working:**
|
|
- Visit: `https://columbiawindows.com/product-finder/css/styles.css`
|
|
- Should return CSS file, not 404
|
|
|
|
**Check .htaccess MIME types:**
|
|
Ensure these lines are in `.htaccess`:
|
|
```apache
|
|
AddType text/css .css
|
|
AddType application/javascript .js
|
|
AddType application/json .json
|
|
```
|
|
|
|
### Issue: Login works but redirects are wrong
|
|
|
|
**Check redirect code in templates:**
|
|
All JavaScript should use `BASE_URL`:
|
|
```javascript
|
|
const BASE_URL = '{{ base_url }}';
|
|
window.location.href = BASE_URL + '/login';
|
|
```
|
|
|
|
All Python redirects should use `url_for()`:
|
|
```python
|
|
return redirect(url_for('login_page'))
|
|
```
|
|
|
|
### Issue: Works locally, fails on server
|
|
|
|
**Verify environment:**
|
|
```bash
|
|
# SSH to server
|
|
cd ~/public_html/product-finder
|
|
python3 -c "import os; print(os.environ.get('APPLICATION_ROOT', 'NOT SET'))"
|
|
```
|
|
|
|
Should print: `/product-finder`
|
|
|
|
**Check Passenger is using correct Python:**
|
|
```bash
|
|
which python3
|
|
# Use this path in PassengerPython directive
|
|
```
|
|
|
|
### Issue: Sessions not persisting
|
|
|
|
**Check SECRET_KEY:**
|
|
```python
|
|
# In config.py, production should have a fixed SECRET_KEY
|
|
SECRET_KEY = 'your-fixed-secret-key-here' # Don't use secrets.token_hex() in production
|
|
```
|
|
|
|
**Check cookie settings:**
|
|
Session cookies need to work with the subdirectory path.
|
|
|
|
### Issue: API calls return 404
|
|
|
|
**Check browser console:**
|
|
Press F12, go to Network tab, and check the actual URLs being called.
|
|
|
|
**Should see:**
|
|
```
|
|
https://columbiawindows.com/product-finder/api/login
|
|
https://columbiawindows.com/product-finder/api/session
|
|
```
|
|
|
|
**If you see:**
|
|
```
|
|
https://columbiawindows.com/api/login ❌ Missing prefix
|
|
```
|
|
|
|
Then `BASE_URL` is not set correctly in template.
|
|
|
|
## ✅ Verification Checklist
|
|
|
|
After deployment, test these in order:
|
|
|
|
- [ ] Visit `https://columbiawindows.com/product-finder/`
|
|
- Should redirect to `/product-finder/login` ✓
|
|
|
|
- [ ] Login page loads
|
|
- CSS styled correctly ✓
|
|
- No 404s in browser console ✓
|
|
|
|
- [ ] Login with Master/Master
|
|
- Should redirect to `/product-finder/select-location` ✓
|
|
|
|
- [ ] Select a location
|
|
- Should redirect to `/product-finder/` ✓
|
|
|
|
- [ ] User info shows in header ✓
|
|
|
|
- [ ] Click "User Management" (if Master user)
|
|
- Should go to `/product-finder/users` ✓
|
|
|
|
- [ ] Logout
|
|
- Should return to `/product-finder/login` ✓
|
|
|
|
## 📝 Key Points
|
|
|
|
1. **The middleware is critical** - It tells Flask about the `/product-finder` prefix
|
|
2. **passenger_wsgi.py sets the env var** - Before importing the app
|
|
3. **All templates use BASE_URL** - For JavaScript fetch calls
|
|
4. **All routes use url_for()** - For Python redirects
|
|
5. **Production config defaults to /product-finder** - Development stays at /
|
|
|
|
## 🔄 Rolling Back
|
|
|
|
If something goes wrong:
|
|
|
|
```bash
|
|
# Remove new files
|
|
rm -rf product-finder
|
|
|
|
# Restore backup
|
|
mv product-finder.backup product-finder
|
|
|
|
# Restart Passenger
|
|
touch product-finder/tmp/restart.txt
|
|
```
|
|
|
|
## 📞 Still Having Issues?
|
|
|
|
Run this diagnostic script on the server:
|
|
|
|
```python
|
|
# Save as test_deployment.py in product-finder folder
|
|
import os
|
|
import sys
|
|
|
|
print("=" * 60)
|
|
print("DEPLOYMENT DIAGNOSTIC")
|
|
print("=" * 60)
|
|
print(f"Python Version: {sys.version}")
|
|
print(f"Current Directory: {os.getcwd()}")
|
|
print(f"APPLICATION_ROOT env: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}")
|
|
print()
|
|
|
|
try:
|
|
os.environ['APPLICATION_ROOT'] = '/product-finder'
|
|
from app import app
|
|
print("✓ App imported successfully")
|
|
print(f"APPLICATION_ROOT config: {app.config.get('APPLICATION_ROOT')}")
|
|
print(f"Middleware applied: {'PrefixMiddleware' in str(type(app.wsgi_app))}")
|
|
except Exception as e:
|
|
print(f"✗ Error importing app: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
```
|
|
|
|
Run with: `python3 test_deployment.py`
|
|
|
|
## 🎯 Expected Behavior
|
|
|
|
**Before these changes:**
|
|
- Redirect works: `/product-finder` → `/product-finder/login` ✓
|
|
- Login page loads BUT Flask doesn't know about `/product-finder` prefix
|
|
- All `url_for()` calls generate `/login` instead of `/product-finder/login` ❌
|
|
- Result: 404 errors on subpages
|
|
|
|
**After these changes:**
|
|
- Flask knows it's at `/product-finder` via middleware ✓
|
|
- All `url_for()` generates `/product-finder/login` ✓
|
|
- All templates use `BASE_URL = '/product-finder'` ✓
|
|
- Result: Everything works ✓
|
|
|
|
## 🆘 Quick Fix Checklist
|
|
|
|
If deployed and not working:
|
|
|
|
1. [ ] Uploaded `app/app.py` with PrefixMiddleware?
|
|
2. [ ] Uploaded `app/passenger_wsgi.py` with env var setting?
|
|
3. [ ] Uploaded `app/.htaccess` with SetEnv directive?
|
|
4. [ ] Ran `touch tmp/restart.txt` to restart Passenger?
|
|
5. [ ] Checked error logs for Python errors?
|
|
6. [ ] Tested `/product-finder/debug-config` route?
|
|
7. [ ] Verified cookies are being set (F12 > Application > Cookies)?
|
|
|
|
If all checked and still failing, check server error logs for the actual Python error.
|