Files
orion/16.jinja2_migration_progress.md

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.py for 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/*.html routes no longer active

3. Exception Handler Updates

  • Updated app/exceptions/handler.py to 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/vendors
  • static/admin/js/login.js - redirects to /admin/dashboard
  • static/admin/js/vendors.js - auth checks use /admin/login
  • static/admin/js/vendor-edit.js - all redirects updated
  • static/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:

  1. After login → redirects back to login
  2. On login page → continuous API calls to /admin/auth/me
  3. Dashboard → redirects to login → redirects to dashboard

Root Causes Identified

  1. Multiple redirect handlers fighting:

    • Server-side: handler.py redirects on 401 for HTML pages
    • Client-side: api-client.js also redirects on 401
    • Both triggering simultaneously
  2. Login page checking auth on init:

    • Calls /admin/auth/me on page load
    • Gets 401 → triggers redirect
    • Creates loop
  3. 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

  1. main.py

    # Added:
    - Jinja2Templates import and configuration
    - admin_pages router include at /admin prefix
    
  2. app/api/main.py (unchanged - just includes v1 routes)

  3. app/api/v1/admin/__init__.py

    # Added:
    - import pages
    - router.include_router(pages.router, tags=["admin-pages"])
    
  4. 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
    
  5. app/routes/frontend.py

    # Changed:
    - Commented out all /admin/ routes
    - Left vendor and shop routes active
    
  6. app/exceptions/handler.py

    # Added:
    - 401 redirect logic for HTML pages
    - _is_html_page_request() helper
    # Status: Needs simplification
    

Frontend Files

  1. static/admin/js/login.js

    // Changed:
    - Removed /static/admin/ paths
    - Updated to /admin/ paths
    - checkExistingAuth() logic
    # Status: Needs simplification
    
  2. static/admin/js/dashboard.js

    // Changed:
    - viewVendor() uses /admin/vendors
    # Status: Working
    
  3. static/admin/js/vendors.js

    // Changed:
    - checkAuth() redirects to /admin/login
    - handleLogout() redirects to /admin/login
    # Status: Not tested yet
    
  4. static/admin/js/vendor-edit.js

    // Changed:
    - All /static/admin/ paths to /admin/
    # Status: Not tested yet
    
  5. static/shared/js/api-client.js

    // Changed:
    - handleUnauthorized() uses /admin/login
    # Status: Needs simplification - causing loops
    
  6. static/shared/js/utils.js (unchanged - working fine)

Template Files (NEW)

  1. app/templates/admin/base.html

    • Master layout with sidebar and header
    • Script loading in correct order
    • No partial-loader.js
  2. app/templates/admin/login.html

    • Standalone login page
    • Alpine.js adminLogin() component
  3. app/templates/admin/dashboard.html

    • Extends base.html
    • Alpine.js adminDashboard() component
  4. app/templates/partials/header.html

    • Top navigation bar
    • Updated logout link to /admin/login
  5. 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:

  1. Simplify login.js:

    // Remove all auth checking on init
    // Just show login form
    // Only redirect after successful login
    
  2. Simplify api-client.js:

    // Remove handleUnauthorized() redirect logic
    // Just throw errors, don't redirect
    // Let server handle redirects
    
  3. 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:

  1. Navigate to /admin/login → should show form (no loops)
  2. Login → should redirect to /admin/dashboard
  3. Dashboard → should load with sidebar/header
  4. No console errors, no 404s for partials

After Auth Works

  1. Create remaining page templates:

    • app/templates/admin/vendors.html
    • app/templates/admin/users.html
    • app/templates/admin/vendor-edit.html
  2. Test all admin flows:

    • Login ✓
    • Dashboard ✓
    • Vendors list
    • Vendor create
    • Vendor edit
    • User management
  3. Cleanup:

    • Remove old static HTML files
    • Remove app/routes/frontend.py admin routes completely
    • Remove partial-loader.js
  4. Migrate vendor portal:

    • Same process for /vendor/* routes
    • Create vendor templates
    • Update vendor JavaScript files

📚 Key Learnings

What Worked

  1. Server-side template rendering - Clean, fast, no AJAX for partials
  2. Jinja2 integration - Easy to set up, works with FastAPI
  3. Route separation - HTML routes in pages.py, API routes separate
  4. Template inheritance - base.html + {% extends %} pattern

What Caused Issues

  1. Multiple redirect handlers - Client + server both handling 401
  2. Auth checking on login page - Created loops
  3. Complex error handling - Too many places making decisions
  4. Path inconsistencies - Old /static/admin/ vs new /admin/

Best Practices Identified

  1. Single source of truth for redirects - Choose server OR client, not both
  2. Login page should be dumb - No auth checking, just show form
  3. API client should be simple - Fetch data, throw errors, don't redirect
  4. Server handles page-level auth - FastAPI dependencies + exception handler
  5. 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

  1. Does the simplified auth flow work without loops?
  2. Can we successfully login and access dashboard?
  3. Are tokens being sent correctly in API requests?
  4. Do we need the auth check on login page at all?
  5. 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.