Skip to content

Latest commit

 

History

History
708 lines (526 loc) · 20.1 KB

File metadata and controls

708 lines (526 loc) · 20.1 KB

Phase 2: Challenges & Solutions Reference

Purpose: Quick reference for technical challenges encountered during Phase 2 and their solutions

When to use this document:

  • 🔍 Debugging a similar issue
  • 📖 Understanding why a specific solution was chosen
  • 🧠 Learning about Vite + code-server proxy configuration
  • 🚀 Quick problem-solving without reading full technical log
  • 🧪 NEW: Running automated diagnostic tests

🔍 Frontend Issue #30 - Search Function Couldn't Find Any Videos (2025-11-05) ✅ FIXED

Problem Statement

Search modal opened but no results appeared, even though videos existed in gallery.

Root Cause

  • Gallery displays: video.original_filename field
  • Search filtered: video.filename field (always null)
  • Result: Search found 0 videos

Solution

File: frontend/src/js/search.js

// performSearch() - line 158
const filename = (video.original_filename || video.filename || '').toLowerCase();

// createVideoResultCard() - line 198
const displayName = video.original_filename || video.filename || 'Untitled Video';

Commit

  • 5558ef2 - fix: Use original_filename in search to match gallery display

🔄 Backend Issue #29 - Intermittent Upload Status "Failed Then Success" (2025-11-05) ✅ FIXED

Problem Statement

Upload shows "failed" for few minutes, then changes to "success" - confusing UX.

Root Cause

Worker tasks re-raised exceptions after marking status "failed", causing Dramatiq to retry (8 times default). On retry, task often succeeded, updating status to "completed".

Solution

File: app/workers/tasks.py

# Before: Re-raised exception, causing retries
except Exception as e:
    supabase.table("videos").update({"status": "failed"}).eq("id", video_id).execute()
    raise  # ← THIS CAUSES DRAMATIQ TO RETRY!

# After: Don't re-raise - failed is final state
except Exception as e:
    supabase.table("videos").update({"status": "failed", "error_message": str(e)}).eq("id", video_id).execute()
    # No re-raise - prevents confusing "failed then success" behavior

Note: For smart retries later: only retry transient errors (timeout, 429), not permanent errors.


🎨 Frontend Issue #25 - Avatar Initials Display Inconsistency (2025-11-03) ✅ FULLY FIXED

Problem Statement

Avatar showed 'TM' on settings but 'V' on other pages.

Root Cause

  • Dashboard & Settings: Fetched fresh /users/me data → correct initials
  • Gallery, Upload, Status: Used stale sessionStorage → incomplete data

Solution

Updated all pages to fetch fresh user data (like dashboard):

File: gallery.js, upload.js, status.js

try {
    const user = await apiClient.makeRequest('/users/me', { method: 'GET' });
    if (user && !user.error) {
        sessionManager.updateAvatarInitials(user);
    }
} catch (error) {
    // Fallback to sessionStorage
    if (sessionManager.user) {
        sessionManager.updateAvatarInitials(sessionManager.user);
    }
}

🧪 Backend Issues #18 & #19 - Password Change Complete Fix (2025-11-01) ✅ FULLY FIXED

Part A: Backend - 422 Unprocessable Entity

Root Cause: Endpoint used password_change: dict instead of Pydantic model. Solution: Created PasswordChange Pydantic model.

Part B: Frontend - Silent Error Failures

Root Cause: Code only checked response.message, never response.error. Solution: Check error first:

if (response && response.error) {
    // handle error
} else if (response && response.message) {
    // handle success
}

Part C: API Client - 401 Redirect Issue

Root Cause: 401 redirect handler caught password validation errors (401), redirecting settings page to login. Solution: Exclude settings.html from auto-redirect:

if (!currentPath.includes('/login.html') && !currentPath.includes('/register.html') && !currentPath.includes('/settings.html')) {
    window.location.href = '/absproxy/3000/src/pages/login.html';
}

Key Learning: 401 can mean: session expired (redirect), authentication failed (show error), or validation failed (show error).


🧪 Backend Issue #20 - Profile & Preferences Forms Broken (2025-11-01) ✅ FULLY FIXED

Root Causes

  1. HTML: Hardcoded placeholders, no IDs on inputs
  2. Backend: /users/me used profile_data: dict instead of Pydantic model
  3. Frontend: Error objects not stringified before display ([object Object])

Solutions

Created Pydantic models:

class ProfileUpdate(BaseModel):
    display_name: Optional[str] = None
    company: Optional[str] = None
    avatar_url: Optional[str] = None

class PreferencesUpdate(BaseModel):
    notifications_enabled: Optional[bool] = None
    marketing_emails: Optional[bool] = None
    default_quality: Optional[str] = None

Key Learning: Always use Pydantic models for request bodies, never raw dict.


🧪 Backend Issues #12-15 - ALL RESOLVED (2025-11-01)

Issue #12: Dual Image Upload Error

Problem: [object Object],[object Object] error Solution: Hybrid parameter passing - context params + clear processing params

Issue #13: Videos Stuck in "Processing"

Problem: 29 videos stuck, oldest 45 days Solution: 3-phase database status tracking in workers

Issue #14: Runware API Unsupported Width Parameter

Problem: unsupportedArchitectureWidth error Solution: Removed width/height from IVideoInference request (Kling doesn't support these)

Issue #15: Pipeline Return Type Mismatch

Problem: AttributeError: 'bool' object has no attribute 'get' Solution: Changed pipeline to return {"video_path": path} instead of boolean


🧪 Automated Testing

Quick Diagnosis (2-3 seconds)

pytest tests/test_debug_integration.py::TestSystemHealthCheck -v -s

Run All Tests

pytest tests/test_debug_endpoints.py -v

Documentation:


🎯 Settings Page Implementation (2025-11-01)

Complete Settings page with profile, security, preferences, and account actions.

Features Implemented

1. Profile Management

  • Update display name and company
  • Fetch from API, real-time validation
  • Backend: PUT /users/me

2. Password Change

  • Real-time validation: current password, strength indicator (Red→Orange→Yellow→Green), match indicator
  • 6-point validation before submit
  • Visual feedback: red border on incorrect password
  • Backend: POST /auth/change-password

3. Preferences

  • Toggles: notifications, marketing emails
  • Quality selector: standard/pro
  • Backend: PUT /users/me/preferences

4. Danger Zone

  • Delete Account: requires "DELETE MY ACCOUNT" confirmation
  • Clear All Videos: requires confirmation
  • Backends: DELETE /users/me, DELETE /videos/clear-all

Security

  • Current password verified server-side via Supabase
  • Account deletion requires exact string match
  • All endpoints require JWT authentication

Challenge 1: Tailwind CSS Not Rendering

Symptom

Unstyled HTML, no Tailwind classes, images not loading.

Root Cause

Complex path resolution conflict:

  • Code-server /proxy/3000/ strips prefix before forwarding
  • Vite base: '/proxy/3000/' expected prefix in requests
  • Mismatch: Vite received /src/main.js, expected /proxy/3000/src/main.js

Solution

  1. Vite Config: base: '/'
  2. HTML: Relative paths (../main.js, ../styles/index.css)
  3. CSS Loading: Direct <link> tag instead of JS import

Challenge 2: JavaScript Modules 404 Errors

Symptom

404 errors for all .js modules, buttons did nothing.

Root Cause

Path-stripping proxy (/proxy/) vs. Vite expecting /proxy/3000/ prefix.

Solution

Switch to path-preserving proxy:

  1. Changed: /proxy/3000//absproxy/3000/
  2. Vite config: base: '/absproxy/3000/'
  3. How it works: /absproxy/ preserves full path when forwarding

Challenge 3: API Requests Failing (405/404)

Symptom

POST /api/v1/auth/login 405 (Method Not Allowed)
GET /api/v1/users/me 404 (Not Found)

Root Cause

  1. API base URL missing /absproxy/3000/ prefix
  2. Vite proxy not matching /absproxy/3000/api paths

Solution

Update API base URL:

const baseUrl = '/absproxy/3000/api/v1';

Add Vite proxy rule:

'/absproxy/3000/api': {
  target: 'http://localhost:5000',
  rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
}

Challenge 4: Dashboard Redirect Loop

Symptom

Login succeeds → dashboard appears briefly → redirects to login.

Root Cause

API login returned 405 error (Challenge 3), but frontend treated as success. No session data saved, so dashboard redirected.

Solution

Fixed by Challenge 3 (correct API URL). Enhanced error handling in login.js.


Challenge 5: Form Submission Hijacking (Security Issue)

Symptom

Credentials appeared in URL: login.html?email=user@example.com&password=mypassword

Root Cause

Race condition: HTML form submitted before JavaScript loaded to prevent default behavior.

Security Implications (CRITICAL)

  • ❌ Passwords in URL bar, browser history, server logs, proxy logs, Referer header

Solution

<form action="javascript:void(0);">

Disables default form submission at HTML level, no race condition.


Challenge 6: Login Error Messages Disappear

Symptom

Error message flashes briefly, then page reloads and error disappears.

Root Cause

API client auto-redirected to login on 401, but user already on login page → reload loop.

Solution

if (response.status === 401) {
    const currentPath = window.location.pathname;
    if (!currentPath.includes('/login.html') && !currentPath.includes('/register.html')) {
        window.location.href = '/absproxy/3000/src/pages/login.html';
    }
}

Challenge 7: Gallery Had Hardcoded Placeholder Videos

Symptom

Gallery showed 3 static example cards instead of real videos.

Solution

Complete rewrite:

HTML: Dynamic containers

<div id="video-grid-container" class="grid...">
  <!-- Videos inserted via JS -->
</div>

JavaScript:

async function loadVideos() {
    const videos = await apiClient.getUserVideos();
    videos.forEach(video => {
        const card = createVideoCard(video);
        gridContainer.appendChild(card);
    });
}

function createVideoCard(video) {
    // Generate HTML from video data
    // Conditional rendering for status badges
    // Smart download button (enabled only when completed)
}

Features: Dynamic rendering, status badges (processing/completed/failed), smart buttons, loading/empty/error states.


Key Technical Learnings

1. Path Resolution in Proxied Vite

  • Path-Stripping (/proxy/): Use base: '/'
  • Path-Preserving (/absproxy/): Use base: '/absproxy/3000/'
  • Best practice: Path-preserving for consistency

2. CSS Loading in Vite

  • ❌ JS import: import './styles/index.css' (transforms to absolute path)
  • ✅ HTML link: <link rel="stylesheet" href="../styles/index.css"> (relative)

3. Vite Proxy for Nested Paths

'/absproxy/3000/api': {
  target: 'http://localhost:5000',
  rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
}

4. Form Security

  • Never rely solely on JavaScript to prevent submission
  • Always use action="javascript:void(0);" + preventDefault()
  • Never use GET for passwords

5. Session Management

  • sessionStorage: Tab-scoped, cleared on close
  • Alternative: localStorage (persists, less secure)
  • Backend always validates JWT

6. Conditional Redirects in API Errors

if (response.status === 401 && !isAuthPage) {
    window.location.href = '/login.html';
}

7. Dynamic UI Rendering

function createCard(data) {
    const card = document.createElement('div');
    card.innerHTML = `
        <h3>${data.title}</h3>
        ${data.ready ? `<a href="${data.url}">Download</a>` : `<button disabled>Processing...</button>`}
    `;
    return card;
}

8. API Validation & Return Types (Issues #14-15)

  • Always check actual API docs, not just SDK types
  • Return types must be consistent (don't mix bool/dict)
  • Use isinstance() checks when mixing types
  • Test with real API to catch validation errors

Configuration Quick Reference

Vite Config

export default defineConfig({
  base: '/absproxy/3000/',
  appType: 'mpa',
  server: {
    port: 3000,
    proxy: {
      '/absproxy/3000/api': {
        target: 'http://localhost:5000',
        rewrite: (path) => path.replace(/^\/absproxy\/3000\/api/, '/api')
      }
    }
  }
});

API Client

const baseUrl = '/absproxy/3000/api/v1';
credentials: 'include'  // Send JWT cookies

HTML Structure

<link rel="stylesheet" href="../styles/index.css">
<form action="javascript:void(0);">
<script type="module" src="../main.js"></script>

Troubleshooting Guide

Unstyled HTML

  1. Check Vite running: ps aux | grep vite
  2. Check base in vite.config.js
  3. Restart: npm run dev

JS Modules 404

  1. Use /absproxy/3000/ in URL
  2. Check base: '/absproxy/3000/' in config
  3. Use relative paths in HTML

API 404/405

  1. Check baseUrl = '/absproxy/3000/api/v1'
  2. Check proxy rule in vite.config.js
  3. Restart backend: docker-compose restart api

Dashboard Redirect Loop

  1. Check login succeeded (Network tab)
  2. Check sessionStorage: sessionStorage.getItem('pm_user')
  3. Re-login

Form Submits with Credentials in URL

  1. Add action="javascript:void(0);" to form

Challenge 8: Debug Endpoints

Custom debug endpoints for troubleshooting (require JWT auth):

  1. GET /debug/supabase-status - Database connectivity
  2. GET /debug/user-videos/{user_id} - User's videos
  3. GET /debug/user-storage/{user_id} - Disk usage
  4. GET /debug/runware-jobs/{user_id} - Processing jobs
  5. GET /debug/gemini-status - Gemini API health
  6. GET /debug/workers-status - Dramatiq workers

Usage:

fetch('/absproxy/3000/api/v1/debug/supabase-status', {credentials: 'include'})
.then(r => r.json())
.then(data => console.log('DB Status:', data))

🧪 Frontend Issues #21-22 - Settings Profile & Preferences (2025-11-01) ✅ FIXED

Issue #21: Profile Update 422 Error

Root Cause: Missing Content-Type: application/json header Solution: Added explicit header to request

Issue #22: Missing Database Columns

Required columns: company, notifications_enabled, marketing_emails, default_quality Solution: Applied migration via Supabase Dashboard

ALTER TABLE users ADD COLUMN IF NOT EXISTS company VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS notifications_enabled BOOLEAN DEFAULT TRUE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS marketing_emails BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS default_quality VARCHAR(50) DEFAULT 'standard';

🧪 Frontend Issue #23 - Company Field Not Persisting (2025-11-02) ✅ FIXED

Problem

Company saved successfully but showed placeholder on page reload.

Root Causes

  1. Frontend didn't call updateProfileUI() after save
  2. UserResponse Pydantic model missing company field - Pydantic stripped it from API response

Solutions

Frontend: Added updateProfileUI(response.user) after save

Backend: Added fields to UserResponse

class UserResponse(BaseModel):
    company: Optional[str] = None  # ADDED
    notifications_enabled: Optional[bool] = None  # ADDED
    marketing_emails: Optional[bool] = None  # ADDED
    default_quality: Optional[str] = None  # ADDED

Key Learning: Pydantic response models actively filter - must include all fields you want returned.


🔐 Security Issues #26-27 - Account Deletion Vulnerability (2025-11-03) ✅ FIXED

Issue #26: Deleted Accounts Can Still Login

Problem: Account deleted but user could still login with old JWT.

Three Root Causes

  1. JWT validation didn't check user existence - Only validated signature
  2. Delete endpoint didn't clear JWT cookies - Browser kept valid token
  3. Frontend session clearing delayed 1.5s - Race condition window

Three-Part Solution

Fix #1: Validate User Existence (CRITICAL)

async def get_current_user(access_token: Optional[str] = Cookie(None)):
    payload = verify_supabase_jwt(access_token)
    user_id = payload.get("sub")

    # THE FIX: Query database
    response = supabase.table("users").select("*").eq("id", user_id).execute()
    if not response.data:
        raise HTTPException(status_code=401, detail="User account no longer exists")
    return response.data[0]

Fix #2: Clear JWT Cookies

async def delete_my_account(response: Response):
    supabase.table("users").delete().eq("id", user_id).execute()
    response.delete_cookie(key="access_token")
    response.delete_cookie(key="refresh_token")

Fix #3: Clear Session Immediately

sessionManager.clearSession();  // Immediate, no delay
setTimeout(() => window.location.href = '/login.html', 1500);  // Only redirect delayed

Issue #27: Account Deletion 500 Error

Problem: Supabase auth deletion permission error Solution: Wrapped in try-catch, gracefully continues. Database deletion is the security boundary.

Security Principles

  1. Never trust tokens alone - validate database state
  2. Defense in depth - multiple layers
  3. Graceful degradation - if one fails, others protect
  4. Clear user communication
  5. Fail-safe design - prioritize blocking access over cleanup

🔍 Discovery #28: Auth User & Database User Sync (2025-11-05) ✅ DOCUMENTED

Issue

Programmatic account creation (Python script, not /register endpoint) only creates auth record, missing database profile.

Two-System Architecture

  1. Supabase Auth - Handles passwords, issues JWTs
  2. users Table - Stores profiles, required by get_current_user()

When only auth created:

  • Login succeeds (auth has credentials)
  • Dashboard loads (JWT valid)
  • API calls fail (user not in database)
  • 401 "User account no longer exists"

Solution

Always create both:

# Step 1: Create auth
auth_response = supabase.auth.sign_up({"email": "...", "password": "..."})

# Step 2: Create database profile (REQUIRED)
supabase.table("users").insert({
    "id": auth_response.user.id,
    "email": "...",
    "display_name": "..."
}).execute()

Best Practice: Use /register endpoint - handles sync automatically.


✅ Backend Issue #31 - Runware Image Transfer Failure (2025-11-06) RESOLVED

Problem

Upload succeeded but processing failed with:

RunwareAPIError: 'failedToTransferImage' - Image URL not publicly accessible

Root Cause (IDENTIFIED)

NOT a code bug. Normal timing validation:

  • Project idle 2 days, Vite dev server stopped
  • Upload attempted anyway (API responsive)
  • Image URL not yet publicly accessible
  • Runware correctly validated: "not accessible"
  • Error was accurate, expected behavior

Solution

Restarted Vite server: pm2 restart frontend Next upload succeeded without code changes.

Runware API Assessment

  • ✅ No reliability issues
  • ✅ 34-62% cheaper than direct Kling ($0.92 vs $1.40)
  • ✅ Clear error messages
  • ✅ Solid choice

Future Enhancement (Optional)

Retry logic with exponential backoff (2s, 5s, 10s) for transient timing issues.

Lessons Learned

  1. Check service status first after inactivity
  2. Runware validation is correct
  3. Timing issues normal in distributed systems
  4. Clear error messages help debugging

Related Documentation

For detailed session logs:PHASE_2_TECHNICAL_LOG.md

For current status:PHASE_2_PROGRESS.md

For implementation guides:JavaScript Integration GuideAPI Endpoints Reference


Document Version: 2.0 (Condensed) Created: 2025-10-12 Last Updated: 2025-11-07 Purpose: Quick reference for technical solutions and known issues