# 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).
