15 KiB
Jinja2 Migration Progress - Admin Panel
Date: October 20, 2025
Project: Multi-Tenant E-commerce Platform
Goal: Migrate from static HTML files to Jinja2 server-rendered templates
🎯 Current Status: DEBUGGING AUTH LOOP
We successfully set up the Jinja2 infrastructure but are experiencing authentication redirect loops. We're in the process of simplifying the auth flow to resolve this.
✅ What's Been Completed
1. Infrastructure Setup ✅
- Added Jinja2Templates to
main.py - Created
app/templates/directory structure - Created
app/api/v1/admin/pages.pyfor HTML routes - Integrated pages router into the main app
Files Created:
app/
├── templates/
│ ├── admin/
│ │ ├── base.html ✅ Created
│ │ ├── login.html ✅ Created
│ │ └── dashboard.html ✅ Created
│ └── partials/
│ ├── header.html ✅ Moved from static
│ └── sidebar.html ✅ Moved from static
└── api/
└── v1/
└── admin/
└── pages.py ✅ Created
2. Route Configuration ✅
New Jinja2 Routes (working):
/admin/→ redirects to/admin/dashboard/admin/login→ login page (no auth)/admin/dashboard→ dashboard page (requires auth)/admin/vendors→ vendors page (requires auth)/admin/users→ users page (requires auth)
Old Static Routes (disabled):
- Commented out admin routes in
app/routes/frontend.py - Old
/static/admin/*.htmlroutes no longer active
3. Exception Handler Updates ✅
- Updated
app/exceptions/handler.pyto redirect HTML requests on 401 - Added
_is_html_page_request()helper function - Server-side redirects working for unauthenticated page access
4. JavaScript Updates ✅
Updated all JavaScript files to use new routes:
Files Updated:
static/admin/js/dashboard.js- viewVendor() uses/admin/vendorsstatic/admin/js/login.js- redirects to/admin/dashboardstatic/admin/js/vendors.js- auth checks use/admin/loginstatic/admin/js/vendor-edit.js- all redirects updatedstatic/shared/js/api-client.js- handleUnauthorized() uses/admin/login
5. Template Structure ✅
Base Template (app/templates/admin/base.html):
- Server-side includes for header and sidebar (no more AJAX loading!)
- Proper script loading order
- Alpine.js integration
- No more
partial-loader.js
Dashboard Template (app/templates/admin/dashboard.html):
- Extends base template
- Uses Alpine.js
adminDashboard()component - Stats cards and recent vendors table
Login Template (app/templates/admin/login.html):
- Standalone page (doesn't extend base)
- Uses Alpine.js
adminLogin()component
❌ Current Problem: Authentication Loop
Issue Description
Getting infinite redirect loops in various scenarios:
- After login → redirects back to login
- On login page → continuous API calls to
/admin/auth/me - Dashboard → redirects to login → redirects to dashboard
Root Causes Identified
-
Multiple redirect handlers fighting:
- Server-side:
handler.pyredirects on 401 for HTML pages - Client-side:
api-client.jsalso redirects on 401 - Both triggering simultaneously
- Server-side:
-
Login page checking auth on init:
- Calls
/admin/auth/meon page load - Gets 401 → triggers redirect
- Creates loop
- Calls
-
Token not being sent properly:
- Token stored but API calls not including it
- Gets 401 even with valid token
Latest Approach (In Progress)
Simplifying to minimal working version:
- Login page does NOTHING on init (no auth checking)
- API client does NOT redirect (just throws errors)
- Server ONLY redirects browser HTML requests (not API calls)
- One source of truth for auth handling
📝 Files Modified (Complete List)
Backend Files
-
main.py# Added: - Jinja2Templates import and configuration - admin_pages router include at /admin prefix -
app/api/main.py(unchanged - just includes v1 routes) -
app/api/v1/admin/__init__.py# Added: - import pages - router.include_router(pages.router, tags=["admin-pages"]) -
app/api/v1/admin/pages.py(NEW FILE)# Contains: - @router.get("/") - root redirect - @router.get("/login") - login page - @router.get("/dashboard") - dashboard page - @router.get("/vendors") - vendors page - @router.get("/users") - users page -
app/routes/frontend.py# Changed: - Commented out all /admin/ routes - Left vendor and shop routes active -
app/exceptions/handler.py# Added: - 401 redirect logic for HTML pages - _is_html_page_request() helper # Status: Needs simplification
Frontend Files
-
static/admin/js/login.js// Changed: - Removed /static/admin/ paths - Updated to /admin/ paths - checkExistingAuth() logic # Status: Needs simplification -
static/admin/js/dashboard.js// Changed: - viewVendor() uses /admin/vendors # Status: Working -
static/admin/js/vendors.js// Changed: - checkAuth() redirects to /admin/login - handleLogout() redirects to /admin/login # Status: Not tested yet -
static/admin/js/vendor-edit.js// Changed: - All /static/admin/ paths to /admin/ # Status: Not tested yet -
static/shared/js/api-client.js// Changed: - handleUnauthorized() uses /admin/login # Status: Needs simplification - causing loops -
static/shared/js/utils.js(unchanged - working fine)
Template Files (NEW)
-
app/templates/admin/base.html✅- Master layout with sidebar and header
- Script loading in correct order
- No partial-loader.js
-
app/templates/admin/login.html✅- Standalone login page
- Alpine.js adminLogin() component
-
app/templates/admin/dashboard.html✅- Extends base.html
- Alpine.js adminDashboard() component
-
app/templates/partials/header.html✅- Top navigation bar
- Updated logout link to /admin/login
-
app/templates/partials/sidebar.html✅- Side navigation menu
- Updated all links to /admin/* paths
🔧 Next Steps (Tomorrow)
Immediate Priority: Fix Auth Loop
Apply the simplified approach from the last message:
-
Simplify
login.js:// Remove all auth checking on init // Just show login form // Only redirect after successful login -
Simplify
api-client.js:// Remove handleUnauthorized() redirect logic // Just throw errors, don't redirect // Let server handle redirects -
Simplify
handler.py:// Only redirect browser HTML requests (text/html accept header) // Don't redirect API calls (application/json) // Don't redirect if already on login page
Test Flow:
- Navigate to
/admin/login→ should show form (no loops) - Login → should redirect to
/admin/dashboard - Dashboard → should load with sidebar/header
- No console errors, no 404s for partials
After Auth Works
-
Create remaining page templates:
app/templates/admin/vendors.htmlapp/templates/admin/users.htmlapp/templates/admin/vendor-edit.html
-
Test all admin flows:
- Login ✓
- Dashboard ✓
- Vendors list
- Vendor create
- Vendor edit
- User management
-
Cleanup:
- Remove old static HTML files
- Remove
app/routes/frontend.pyadmin routes completely - Remove
partial-loader.js
-
Migrate vendor portal:
- Same process for
/vendor/*routes - Create vendor templates
- Update vendor JavaScript files
- Same process for
📚 Key Learnings
What Worked
- ✅ Server-side template rendering - Clean, fast, no AJAX for partials
- ✅ Jinja2 integration - Easy to set up, works with FastAPI
- ✅ Route separation - HTML routes in
pages.py, API routes separate - ✅ Template inheritance -
base.html+{% extends %}pattern
What Caused Issues
- ❌ Multiple redirect handlers - Client + server both handling 401
- ❌ Auth checking on login page - Created loops
- ❌ Complex error handling - Too many places making decisions
- ❌ Path inconsistencies - Old
/static/admin/vs new/admin/
Best Practices Identified
- Single source of truth for redirects - Choose server OR client, not both
- Login page should be dumb - No auth checking, just show form
- API client should be simple - Fetch data, throw errors, don't redirect
- Server handles page-level auth - FastAPI dependencies + exception handler
- Clear separation - HTML pages vs API endpoints
🗂️ Project Structure (Current)
project/
├── main.py ✅ Updated
├── app/
│ ├── api/
│ │ ├── main.py ✅ Unchanged
│ │ └── v1/
│ │ └── admin/
│ │ ├── __init__.py ✅ Updated
│ │ ├── pages.py ✅ NEW
│ │ ├── auth.py ✅ Existing (API routes)
│ │ ├── vendors.py ✅ Existing (API routes)
│ │ └── dashboard.py ✅ Existing (API routes)
│ ├── routes/
│ │ └── frontend.py ⚠️ Partially disabled
│ ├── exceptions/
│ │ └── handler.py ⚠️ Needs simplification
│ └── templates/ ✅ NEW
│ ├── admin/
│ │ ├── base.html
│ │ ├── login.html
│ │ └── dashboard.html
│ └── partials/
│ ├── header.html
│ └── sidebar.html
└── static/
├── admin/
│ ├── js/
│ │ ├── login.js ⚠️ Needs simplification
│ │ ├── dashboard.js ✅ Updated
│ │ ├── vendors.js ✅ Updated
│ │ └── vendor-edit.js ✅ Updated
│ └── css/
│ └── tailwind.output.css ✅ Unchanged
└── shared/
└── js/
├── api-client.js ⚠️ Needs simplification
├── utils.js ✅ Working
└── icons.js ✅ Working
Legend:
- ✅ = Working correctly
- ⚠️ = Needs attention/debugging
- ❌ = Not working/causing issues
🐛 Debug Commands
Clear localStorage (Browser Console)
localStorage.clear();
Check stored tokens
console.log('admin_token:', localStorage.getItem('admin_token'));
console.log('admin_user:', localStorage.getItem('admin_user'));
Test API call manually
fetch('/api/v1/admin/auth/me', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('admin_token')}`
}
}).then(r => r.json()).then(d => console.log(d));
Check current route
console.log('Current path:', window.location.pathname);
console.log('Full URL:', window.location.href);
📖 Reference: Working Code Snippets
Minimal Login.js (To Try Tomorrow)
function adminLogin() {
return {
dark: false,
credentials: { username: '', password: '' },
loading: false,
error: null,
success: null,
errors: {},
init() {
this.dark = localStorage.getItem('theme') === 'dark';
// NO AUTH CHECKING - just show form
},
async handleLogin() {
if (!this.validateForm()) return;
this.loading = true;
try {
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: this.credentials.username,
password: this.credentials.password
})
});
const data = await response.json();
if (!response.ok) throw new Error(data.message);
localStorage.setItem('admin_token', data.access_token);
localStorage.setItem('admin_user', JSON.stringify(data.user));
this.success = 'Login successful!';
setTimeout(() => window.location.href = '/admin/dashboard', 500);
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
}
}
Simplified API Client Request Method
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
...options,
headers: this.getHeaders(options.headers)
};
const response = await fetch(url, config);
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Request failed');
}
return data;
// NO REDIRECT LOGIC HERE!
}
Simplified Exception Handler
if exc.status_code == 401:
accept_header = request.headers.get("accept", "")
is_browser = "text/html" in accept_header
if is_browser and not request.url.path.endswith("/login"):
if request.url.path.startswith("/admin"):
return RedirectResponse(url="/admin/login", status_code=302)
# Return JSON for API calls
return JSONResponse(status_code=exc.status_code, content=exc.to_dict())
💡 Questions to Answer Tomorrow
- Does the simplified auth flow work without loops?
- Can we successfully login and access dashboard?
- Are tokens being sent correctly in API requests?
- Do we need the auth check on login page at all?
- Should we move ALL redirect logic to server-side?
🎯 Success Criteria
The migration will be considered successful when:
- Login page loads without loops
- Login succeeds and redirects to dashboard
- Dashboard displays with sidebar and header
- No 404 errors for partials
- Icons display correctly
- Stats cards load data from API
- Navigation between admin pages works
- Logout works correctly
End of Session - October 20, 2025
Good work today! We made significant progress on the infrastructure. Tomorrow we'll resolve the auth loop and complete the admin panel migration.