104 lines
4.3 KiB
Markdown
104 lines
4.3 KiB
Markdown
# 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/thumbnail` → `http://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
|