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
- Vite proxy configuration strips the
/apiprefix before forwarding requests to the backend - The backend was returning thumbnail URLs with
/apiprefix:/api/files/projects/1/thumbnail - The frontend was constructing full URLs like
http://localhost:8000/api/files/projects/1/thumbnail, bypassing the Vite proxy - This caused 404 errors because the backend routes don't have an
/apiprefix
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
apiClientimport - Added
thumbnailBlobUrlref to store the blob URL - Implemented
loadThumbnail()function that:- Fetches the thumbnail via
apiClient.get()withresponseType: 'blob' - Creates a blob URL with
URL.createObjectURL() - Properly handles authentication headers through apiClient
- Fetches the thumbnail via
- Updated
getThumbnailUrl()to return the blob URL - Added lifecycle hooks:
onMounted()to load thumbnail on component mountwatch()to reload whencurrentThumbnailUrlprop changesonUnmounted()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
apiClientimport - Added
thumbnailBlobUrlsMap 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
- User uploads thumbnail → Backend saves file and returns:
/files/projects/1/thumbnail - Frontend receives URL and stores it
- Component loads thumbnail via
apiClient.get('/files/projects/1/thumbnail')with blob response - Creates blob URL and displays image
Display Flow (ProjectsView)
- Backend returns:
/files/projects/1/thumbnail - Frontend adds
/apiprefix:/api/files/projects/1/thumbnail - Browser requests:
/api/files/projects/1/thumbnail - Vite proxy strips
/apiand forwards:/files/projects/1/thumbnail→http://localhost:8000/files/projects/1/thumbnail - Backend serves the file correctly ✅
Reference Implementation
This implementation follows the same pattern as task attachments:
frontend/src/components/task/AttachmentCard.vuefrontend/src/components/task/SubmissionCard.vue
Both use blob URLs with proper lifecycle management for displaying thumbnails with authentication.
Testing
- Upload a project thumbnail in project settings
- Verify it displays in the upload component
- Navigate to projects list and verify thumbnail displays
- Refresh page and verify thumbnail persists
- Remove thumbnail and verify placeholder shows
- Check browser console for no 404 errors
Files Modified
backend/routers/projects.py- Fixed 4 thumbnail URL responsesfrontend/src/components/project/ProjectThumbnailUpload.vue- Implemented blob URL patternfrontend/src/views/ProjectsView.vue- Fixed URL transformation