Feat: Replace UUID image identifiers with 8-character base62 short IDs
Short IDs become the canonical identifier in URLs (/i/:short_id), MinIO/R2 storage keys, and all API responses. Hash-based deduplication is preserved. Includes two-phase Alembic migration (003 adds nullable column, 004 enforces NOT NULL) with a backfill script to copy storage objects and populate short_id for existing images. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,15 +17,15 @@ function makeActivatedRoute(queryParams: Record<string, string> = {}) {
|
||||
|
||||
const EMPTY_PAGE = { items: [], total: 0, limit: 24, 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, file_url: '/api/v1/images/1/file', thumbnail_url: null, created_at: '' }],
|
||||
items: [{ id: '1', short_id: 'ShrtImg1', filename: 'a.jpg', tags: ['cat'], hash: '', mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1, storage_key: 'ShrtImg1', thumbnail_key: null, file_url: '/api/v1/i/ShrtImg1/file', thumbnail_url: null, created_at: '' }],
|
||||
total: 1, limit: 24, offset: 0,
|
||||
};
|
||||
const MULTI_PAGE = {
|
||||
items: Array(24).fill(null).map((_, i) => ({
|
||||
id: String(i + 1), filename: `img${i + 1}.jpg`, tags: [], hash: '',
|
||||
id: String(i + 1), short_id: `Shrt${String(i + 1).padStart(4, '0')}`, filename: `img${i + 1}.jpg`, tags: [], hash: '',
|
||||
mime_type: 'image/jpeg', size_bytes: 1, width: 1, height: 1,
|
||||
storage_key: '', thumbnail_key: null,
|
||||
file_url: `/api/v1/images/${i + 1}/file`, thumbnail_url: null, created_at: '',
|
||||
storage_key: `Shrt${String(i + 1).padStart(4, '0')}`, thumbnail_key: null,
|
||||
file_url: `/api/v1/i/Shrt${String(i + 1).padStart(4, '0')}/file`, thumbnail_url: null, created_at: '',
|
||||
})),
|
||||
total: 48, limit: 24, offset: 0,
|
||||
};
|
||||
@@ -292,4 +292,16 @@ describe('LibraryComponent', () => {
|
||||
queryParamsHandling: 'merge',
|
||||
}));
|
||||
});
|
||||
|
||||
it('clicking an image card navigates to /i/:short_id', () => {
|
||||
const fixture = TestBed.createComponent(LibraryComponent);
|
||||
const imgSvc = TestBed.inject(ImageService);
|
||||
spyOn(imgSvc, 'list').and.returnValue(of(ONE_IMAGE));
|
||||
fixture.detectChanges();
|
||||
const router = TestBed.inject(Router);
|
||||
spyOn(router, 'navigate');
|
||||
const card = (fixture.nativeElement as HTMLElement).querySelector('.image-card') as HTMLElement;
|
||||
card.click();
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/i', 'ShrtImg1']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user