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:
@@ -9,9 +9,9 @@ import { ToastService } from '../services/toast.service';
|
||||
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, file_url: '/api/v1/images/img-1/file', thumbnail_url: null,
|
||||
id: 'img-1', short_id: 'AbCd1234', hash: 'abc', filename: 'test.jpg', mime_type: 'image/jpeg',
|
||||
size_bytes: 100, width: 10, height: 10, storage_key: 'AbCd1234',
|
||||
thumbnail_key: null, file_url: '/api/v1/i/AbCd1234/file', thumbnail_url: null,
|
||||
created_at: '2026-01-01T00:00:00Z', tags: ['cat', 'funny'],
|
||||
};
|
||||
const MOCK_IMAGE_ABS = { ...MOCK_IMAGE, file_url: 'https://cdn.example.com/img-1.jpg' };
|
||||
@@ -39,14 +39,14 @@ describe('DetailComponent', () => {
|
||||
const { component, imgSvc } = setup();
|
||||
spyOn(imgSvc, 'updateTags').and.returnValue(of({ ...MOCK_IMAGE, tags: ['funny'] }));
|
||||
component.removeTag('cat');
|
||||
expect(imgSvc.updateTags).toHaveBeenCalledWith('img-1', ['funny']);
|
||||
expect(imgSvc.updateTags).toHaveBeenCalledWith('AbCd1234', ['funny']);
|
||||
});
|
||||
|
||||
it('should call PATCH with new tag included on addTag', () => {
|
||||
const { component, imgSvc } = setup();
|
||||
spyOn(imgSvc, 'updateTags').and.returnValue(of({ ...MOCK_IMAGE, tags: ['cat', 'funny', 'new'] }));
|
||||
component.addTag('new');
|
||||
expect(imgSvc.updateTags).toHaveBeenCalledWith('img-1', ['cat', 'funny', 'new']);
|
||||
expect(imgSvc.updateTags).toHaveBeenCalledWith('AbCd1234', ['cat', 'funny', 'new']);
|
||||
});
|
||||
|
||||
it('should call DELETE and navigate to library on confirm delete', () => {
|
||||
@@ -55,7 +55,7 @@ describe('DetailComponent', () => {
|
||||
spyOn(router, 'navigate');
|
||||
spyOn(imgSvc, 'delete').and.returnValue(of(undefined));
|
||||
component.confirmDelete();
|
||||
expect(imgSvc.delete).toHaveBeenCalledWith('img-1');
|
||||
expect(imgSvc.delete).toHaveBeenCalledWith('AbCd1234');
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/']);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user