Feat: Serve images directly from Cloudflare R2 CDN
API responses now include file_url and thumbnail_url fields. When S3_PUBLIC_BASE_URL is configured, these point to the CDN domain; when unset, they fall back to the existing API proxy paths so local dev requires no additional setup. UI updated to use response URL fields directly instead of constructing proxy URLs client-side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ import { routes } from '../app.routes';
|
||||
const MOCK_IMAGE = {
|
||||
id: 'img-1', hash: 'abc', filename: 'test.jpg', mime_type: 'image/jpeg',
|
||||
size_bytes: 100, width: 10, height: 10, storage_key: 'abc',
|
||||
thumbnail_key: null, created_at: '2026-01-01T00:00:00Z', tags: ['cat', 'funny'],
|
||||
thumbnail_key: null, file_url: '/api/v1/images/img-1/file', thumbnail_url: null,
|
||||
created_at: '2026-01-01T00:00:00Z', tags: ['cat', 'funny'],
|
||||
};
|
||||
|
||||
describe('DetailComponent', () => {
|
||||
|
||||
@@ -49,7 +49,7 @@ const PLACEHOLDER_SVG = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/s
|
||||
|
||||
<img
|
||||
class="full-image"
|
||||
[src]="imageService.getFileUrl(image.id)"
|
||||
[src]="image.file_url"
|
||||
[alt]="image.filename"
|
||||
(error)="onImgError($event)"
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@ function makeActivatedRoute(queryParams: Record<string, string> = {}) {
|
||||
|
||||
const EMPTY_PAGE = { items: [], total: 0, limit: 50, offset: 0 };
|
||||
const ONE_IMAGE = {
|
||||
items: [{ id: '1', filename: 'a.jpg', tags: ['cat'], hash: '', mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1, storage_key: '', thumbnail_key: null, created_at: '' }],
|
||||
items: [{ id: '1', filename: 'a.jpg', tags: ['cat'], hash: '', mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1, storage_key: '', thumbnail_key: null, file_url: '/api/v1/images/1/file', thumbnail_url: null, created_at: '' }],
|
||||
total: 1, limit: 50, offset: 0,
|
||||
};
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ const PLACEHOLDER_SVG = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/s
|
||||
(keydown.enter)="router.navigate(['/images', img.id])"
|
||||
>
|
||||
<img
|
||||
[src]="imageService.getThumbnailUrl(img.id)"
|
||||
[src]="img.thumbnail_url ?? img.file_url"
|
||||
[alt]="img.filename"
|
||||
loading="lazy"
|
||||
(error)="onImgError($event)"
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface ImageRecord {
|
||||
height: number;
|
||||
storage_key: string;
|
||||
thumbnail_key: string | null;
|
||||
file_url: string;
|
||||
thumbnail_url: string | null;
|
||||
created_at: string;
|
||||
tags: string[];
|
||||
duplicate?: boolean;
|
||||
@@ -51,14 +53,6 @@ export class ImageService {
|
||||
return this.http.get<ImageRecord>(`${this.base}/images/${id}`);
|
||||
}
|
||||
|
||||
getFileUrl(id: string): string {
|
||||
return `${this.base}/images/${id}/file`;
|
||||
}
|
||||
|
||||
getThumbnailUrl(id: string): string {
|
||||
return `${this.base}/images/${id}/thumbnail`;
|
||||
}
|
||||
|
||||
updateTags(id: string, tags: string[]): Observable<ImageRecord> {
|
||||
return this.http.patch<ImageRecord>(`${this.base}/images/${id}/tags`, { tags });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user