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:
2026-05-10 00:13:55 +00:00
parent 87eb2703f5
commit 61d923d5be
41 changed files with 1445 additions and 137 deletions

View File

@@ -211,7 +211,7 @@ export class DetailComponent implements OnInit {
removeTag(tag: string): void {
if (!this.image) return;
const updated = this.image.tags.filter((t) => t !== tag);
this.imageService.updateTags(this.image.id, updated).subscribe({
this.imageService.updateTags(this.image.short_id, updated).subscribe({
next: (img) => { this.image = img; this.tagError = ''; this.cdr.markForCheck(); },
error: (err) => { this.tagError = err?.error?.detail ?? 'Failed to remove tag'; this.cdr.markForCheck(); },
});
@@ -221,7 +221,7 @@ export class DetailComponent implements OnInit {
if (!this.image || !tag.trim()) return;
const normalised = tag.trim().toLowerCase();
const updated = [...this.image.tags, normalised];
this.imageService.updateTags(this.image.id, updated).subscribe({
this.imageService.updateTags(this.image.short_id, updated).subscribe({
next: (img) => { this.image = img; this.newTagInput = ''; this.tagError = ''; this.cdr.markForCheck(); },
error: (err) => { this.tagError = err?.error?.detail ?? 'Invalid tag'; this.cdr.markForCheck(); },
});
@@ -232,7 +232,7 @@ export class DetailComponent implements OnInit {
confirmDelete(): void {
if (!this.image) return;
this.imageService.delete(this.image.id).subscribe({
this.imageService.delete(this.image.short_id).subscribe({
next: () => this.router.navigate(['/']),
error: () => { this.showDeleteDialog = false; this.cdr.markForCheck(); },
});