refactor: add AppError helpers and harden upload/download error handling

- refactor: replace inline AppError literals with model.New*Error
  constructors in service and handler layers
- feat: PromoteStaged falls back to copy/delete when os.Rename returns
  EXDEV (cross-device link)
- fix: upload error messages no longer leak internal multipart field
  names or parser implementation details
- fix: upload size violations are converted to domain error
  ErrUploadTooLarge at the handler boundary
- fix: download logs error and size mismatch when io.Copy fails or
  writes fewer bytes than expected
- test: add tests for field name leak prevention, parser error
  sanitization, read errors after response start, and EXDEV fallback
This commit is contained in:
2026-07-05 17:35:34 +08:00
parent fc2b9312fa
commit 28e17a5b08
7 changed files with 305 additions and 47 deletions
+2 -1
View File
@@ -81,8 +81,9 @@
| Staging before promotion | Storage backends write upload bytes to a staging path first, then promote the object to the long-term data path only after validation succeeds. |
| Promote before DB create | The service promotes the object before creating the active file record, preventing visible DB rows from pointing at missing objects. If DB creation fails after promotion, the service best-effort deletes the promoted object. |
| Upload parent location | Multipart upload `parent_id` is passed as a query parameter, keeping the multipart body focused on the file stream. |
| Service-layer upload errors | HTTP transport errors are converted at the handler boundary; the file service only handles domain errors such as oversized uploads. |
**Consequences**:
- Interrupted, malformed, and oversized uploads leave only staging objects, which are safe to clean by path prefix and age.
- Local storage can promote with `os.Rename`; future S3 storage can implement promotion with copy/delete while keeping business visibility controlled by the DB row.
- Local storage promotes with `os.Rename` and falls back to copy/delete on cross-device `EXDEV`; future S3 storage can implement promotion with copy/delete while keeping business visibility controlled by the DB row.
- A DB failure after promotion can still leave a long-term orphan object, but it is not visible through the file API and can be cleaned independently.