LinkDesk/frontend/docs/project-thumbnail-url-fix.md

4.3 KiB

Project Thumbnail URL Fix

Issue

Project thumbnails were not displaying correctly due to URL routing mismatches between the frontend and backend.

Root Cause

  1. Vite proxy configuration strips the /api prefix before forwarding requests to the backend
  2. The backend was returning thumbnail URLs with /api prefix: /api/files/projects/1/thumbnail
  3. The frontend was constructing full URLs like http://localhost:8000/api/files/projects/1/thumbnail, bypassing the Vite proxy
  4. This caused 404 errors because the backend routes don't have an /api prefix

Solution

Backend Changes (backend/routers/projects.py)

Changed all thumbnail URL responses from /api/files/projects/{id}/thumbnail to /files/projects/{id}/thumbnail in 4 locations:

  • list_projects() endpoint (line ~95)
  • get_project() endpoint (line ~175)
  • update_project() endpoint (line ~282)
  • upload_project_thumbnail() endpoint (line ~1193)

This matches the actual backend route structure in backend/routers/files.py.

Frontend Changes

1. ProjectThumbnailUpload Component

File: frontend/src/components/project/ProjectThumbnailUpload.vue

Pattern: Following the task attachment implementation pattern using blob URLs

Changes:

  • Added apiClient import
  • Added thumbnailBlobUrl ref to store the blob URL
  • Implemented loadThumbnail() function that:
    • Fetches the thumbnail via apiClient.get() with responseType: 'blob'
    • Creates a blob URL with URL.createObjectURL()
    • Properly handles authentication headers through apiClient
  • Updated getThumbnailUrl() to return the blob URL
  • Added lifecycle hooks:
    • onMounted() to load thumbnail on component mount
    • watch() to reload when currentThumbnailUrl prop changes
    • onUnmounted() to clean up blob URL and prevent memory leaks

Benefits:

  • Proper authentication handling through apiClient interceptors
  • Memory-efficient with proper cleanup
  • Consistent with task attachment implementation
  • Better error handling

2. ProjectsView Component

File: frontend/src/views/ProjectsView.vue

Pattern: Blob URLs for multiple thumbnails with authentication

Changes:

  • Added apiClient import
  • Added thumbnailBlobUrls Map to store blob URLs by project ID
  • Implemented loadThumbnail() function to fetch individual thumbnails
  • Implemented loadAllThumbnails() to load thumbnails for all visible projects
  • Updated getThumbnailUrl() to return blob URL from the Map
  • Added watch() to reload thumbnails when projects change
  • Added onUnmounted() to clean up all blob URLs
  • Removed loading="lazy" since we're managing loading explicitly

Why blob URLs?:

  • The thumbnail endpoint requires authentication
  • <img> tags don't send auth headers
  • Blob URLs allow us to fetch with auth via apiClient, then display without auth
  • Consistent with task attachment implementation

How It Works Now

Upload Flow

  1. User uploads thumbnail → Backend saves file and returns: /files/projects/1/thumbnail
  2. Frontend receives URL and stores it
  3. Component loads thumbnail via apiClient.get('/files/projects/1/thumbnail') with blob response
  4. Creates blob URL and displays image

Display Flow (ProjectsView)

  1. Backend returns: /files/projects/1/thumbnail
  2. Frontend adds /api prefix: /api/files/projects/1/thumbnail
  3. Browser requests: /api/files/projects/1/thumbnail
  4. Vite proxy strips /api and forwards: /files/projects/1/thumbnailhttp://localhost:8000/files/projects/1/thumbnail
  5. Backend serves the file correctly

Reference Implementation

This implementation follows the same pattern as task attachments:

  • frontend/src/components/task/AttachmentCard.vue
  • frontend/src/components/task/SubmissionCard.vue

Both use blob URLs with proper lifecycle management for displaying thumbnails with authentication.

Testing

  1. Upload a project thumbnail in project settings
  2. Verify it displays in the upload component
  3. Navigate to projects list and verify thumbnail displays
  4. Refresh page and verify thumbnail persists
  5. Remove thumbnail and verify placeholder shows
  6. Check browser console for no 404 errors

Files Modified

  • backend/routers/projects.py - Fixed 4 thumbnail URL responses
  • frontend/src/components/project/ProjectThumbnailUpload.vue - Implemented blob URL pattern
  • frontend/src/views/ProjectsView.vue - Fixed URL transformation