Jul 14 - Using CDN - Phase 1
This commit is contained in:
@@ -1349,19 +1349,21 @@ timeout = 30
|
||||
- [ ] Record the baseline number: **"N photos must still resolve after cutover."**
|
||||
- [ ] Investigate any `missing_on_disk` entries — these are PRE-EXISTING broken references, surfaced now so they can't be blamed on the move.
|
||||
|
||||
### Phase 1 — Storage abstraction + local backend (NO-OP, safe to deploy)
|
||||
- [ ] `app/utils/storage.py` — interface: `save(file_obj, subfolder) -> key`, `read(key) -> bytes`, `delete(key)`, `exists(key) -> bool`, `media_url(key) -> str`.
|
||||
- [ ] `LocalBackend` = byte-for-byte current behavior (`static/uploads` write; `media_url` = `url_for('static', filename=key)`).
|
||||
- [ ] Config: `STORAGE_BACKEND=local|s3` (default `local`).
|
||||
- [ ] Route `_save_photo()` in `inspections.py` + `issues.py`, and `api/photos.py`, through `storage.save()` (keep magic-byte check in the caller).
|
||||
- [ ] Templates: swap `url_for('static', filename=path)` → `media_url(path)` in `issues/view.html`, `inspections/view.html`, and any other photo render sites.
|
||||
- [ ] Deploy with `STORAGE_BACKEND=local` → identical behavior; regression check that web + iPad photos still load.
|
||||
### Phase 1 — Storage abstraction + local backend (NO-OP, safe to deploy) — ✅ delivered
|
||||
- [x] `app/utils/storage.py` — interface: `save(file_obj, subfolder) -> key`, `read(key) -> bytes`, `delete(key)`, `exists(key) -> bool`, `abs_local_path(key)`, `media_url(key) -> str`. Backend chosen by `STORAGE_BACKEND`, one instance cached in `app.extensions`.
|
||||
- [x] `LocalBackend` = byte-for-byte current behavior (`static/uploads` write; `media_url` = `url_for('static', filename=key)`; `abs_local_path` = `static_folder/key`).
|
||||
- [x] Config: `STORAGE_BACKEND=local|s3` (default `local`) in `config.py`.
|
||||
- [x] Routed `_save_photo()` (inspections.py) + `api/photos.py::upload_photo` through `storage.save()` (magic-byte / ext validation stays in the callers).
|
||||
- [x] `media_url` registered as a Jinja global in `app/__init__.py`; swapped all 8 photo render sites: `issues/view.html` (×6: primary, mobile, result), `inspections/view.html` (×1: form image), `inspections/execute.html` (×1).
|
||||
- [ ] Deploy with `STORAGE_BACKEND=local` → identical behavior; regression check that web + iPad photos still load and new uploads still save.
|
||||
- **Deferred to Phase 2 (not yet routed through storage):** photo *deletes* (issues.py ~1013, inspections.py ~1340) and PDF-export image *reads* (`utils/pdf_export.py`). These still hit local disk directly — fine while `local`, must be routed before the `s3` flip. **Bug to fix when routing:** the inspection-delete cleanup builds `os.path.join(UPLOAD_FOLDER, '..', 'static', rel_path)` which normalizes to `app/static/static/...` (double `static`) and never deletes — routing it through `storage.delete(key)` fixes it. The issue-delete path (`root_path + 'static' + rel_path`) is already correct.
|
||||
|
||||
### Phase 2 — R2 backend (build, do NOT flip yet)
|
||||
- [ ] `pip install boto3`; add to `requirements.txt`.
|
||||
- [ ] `.env`: `STORAGE_BACKEND`, `R2_ENDPOINT_URL`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_PRESIGN_TTL=86400`.
|
||||
- [ ] `S3Backend`: boto3 client (`endpoint_url=R2_ENDPOINT_URL`, `region_name='auto'`). `save` → `put_object` (set `ContentType`); `media_url` → `generate_presigned_url('get_object', ..., ExpiresIn=TTL)`; `read` → `get_object`; `exists` → `head_object`.
|
||||
- [ ] Fallback: if `head_object` 404s, `S3Backend.read`/`media_url` fall back to the local static path.
|
||||
- [ ] Route the deferred callers through storage (required before the flip): photo *deletes* (issues.py, inspections.py) → `storage.delete(key)`; PDF-export image *reads* (`utils/pdf_export.py`, `os.path.join(static_folder, v)`) → `storage.read(key)`. Fix the inspection-delete double-`static` path in the process.
|
||||
- [ ] Leave `STORAGE_BACKEND=local` in production for now.
|
||||
|
||||
### Phase 3 — Verified migration sync
|
||||
|
||||
Reference in New Issue
Block a user