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:
77
specs/017-short-id-migration/data-model.md
Normal file
77
specs/017-short-id-migration/data-model.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Data Model: Short Image IDs
|
||||
|
||||
## Changed Entity: Image
|
||||
|
||||
### New Column
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|------------|--------------|------------------------------|-------------------------------------------|
|
||||
| `short_id` | VARCHAR(8) | UNIQUE, NOT NULL (post-migration), INDEX | Base62 alphanumeric, 8 characters |
|
||||
|
||||
### Updated Columns (values change, types unchanged)
|
||||
|
||||
| Column | Old values | New values |
|
||||
|-----------------|-----------------------------------------|-----------------------------------|
|
||||
| `storage_key` | SHA-256 hash (64 hex chars) | short_id (8 base62 chars) |
|
||||
| `thumbnail_key` | `{hash}-thumb` (69 chars) | `{short_id}-thumb` (13 chars) |
|
||||
|
||||
### Unchanged Columns
|
||||
|
||||
| Column | Notes |
|
||||
|------------|-----------------------------------------------------------------------|
|
||||
| `id` | UUID primary key — unchanged, retained as internal identifier |
|
||||
| `hash` | SHA-256 content hash — unchanged, still used for deduplication |
|
||||
| `filename` | Unchanged |
|
||||
| `mime_type`| Unchanged |
|
||||
| `size_bytes`, `width`, `height` | Unchanged |
|
||||
| `created_at` | Unchanged |
|
||||
|
||||
### Validation Rules
|
||||
|
||||
- `short_id`: exactly 8 characters, matching `[a-zA-Z0-9]{8}` — generated on insert, never updated
|
||||
- `short_id` must be unique across all image records
|
||||
- On collision (rare), a new value is generated and retried (up to 10 attempts)
|
||||
|
||||
---
|
||||
|
||||
## Alembic Migrations
|
||||
|
||||
### Migration 003 — Add `short_id` column (nullable)
|
||||
|
||||
```
|
||||
ALTER TABLE images ADD COLUMN short_id VARCHAR(8) NULL;
|
||||
CREATE UNIQUE INDEX ix_images_short_id ON images (short_id);
|
||||
```
|
||||
|
||||
Run immediately on deploy. Existing rows get `short_id = NULL`. New uploads will populate `short_id` on insert (application-level).
|
||||
|
||||
### Migration Script — Backfill existing rows
|
||||
|
||||
`api/scripts/migrate_to_short_ids.py`
|
||||
|
||||
For each image where `short_id IS NULL`:
|
||||
1. Generate 8-char base62 short_id (retry on collision)
|
||||
2. Copy storage object: `{hash}` → `{short_id}` (S3 copy)
|
||||
3. Copy thumbnail if present: `{hash}-thumb` → `{short_id}-thumb`
|
||||
4. Verify new objects exist (S3 head_object)
|
||||
5. Update DB row: `short_id = {short_id}`, `storage_key = {short_id}`, `thumbnail_key = {short_id}-thumb` (or NULL)
|
||||
6. Delete old storage objects
|
||||
|
||||
### Migration 004 — Add NOT NULL constraint
|
||||
|
||||
```
|
||||
ALTER TABLE images ALTER COLUMN short_id SET NOT NULL;
|
||||
```
|
||||
|
||||
Run only after the migration script completes successfully with zero `short_id IS NULL` rows remaining.
|
||||
|
||||
---
|
||||
|
||||
## Storage Object Naming Convention
|
||||
|
||||
| Object type | Key pattern | Example |
|
||||
|-------------|---------------------|-------------------|
|
||||
| Original | `{short_id}` | `xK7mN2pQ` |
|
||||
| Thumbnail | `{short_id}-thumb` | `xK7mN2pQ-thumb` |
|
||||
|
||||
No folder structure. Flat bucket layout (unchanged from current convention).
|
||||
Reference in New Issue
Block a user