* feat: add stream versioning endpoint
Add POST /streams/version/{objectId}/{fileName} to upload a new version
of a file for an existing media object. The version counter increments
automatically (MAX+1); duplicate uploads (same SHA1) are rejected with
409 Conflict. MediaTable sorts streams DESC by version so GET always
returns the latest version first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: pass file data to SaveEntityAction in uploadNewVersion
SaveEntityAction always calls patchEntity() internally; passing data:[]
caused _required validation to fail for file_name, mime_type, contents.
Read body once as string, compute sha1 for duplicate detection, then pass
full data array to SaveEntityAction.
* test: add tests for StreamsTable::nextVersion() and Media streams sort
- StreamsTableTest: testNextVersion() covers objects with/without existing streams
- MediaTableTest: testStreamsOrderedByVersionDesc() verifies streams are
returned sorted by version DESC when contained in a Media entity
* fix: coding style violations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: stream versioning constraints and replace flow
- Add unique index on (object_id, version) in streams table via migration
- Fix nextVersion(): use single query object instead of nested find()
- uploadNewVersion(): check duplicate hash against latest version only,
not all versions; compute nextVersion from same query (one less DB call)
- StreamsTable::beforeSave(): auto-assign nextVersion when a stream is
linked to an object for the first time via relationship PATCH, so the
replace flow (delete + upload + patch relationship) preserves the correct
version number instead of resetting to 1
- StreamsController::resource(): block DELETE on non-latest versioned
streams; standalone streams (no object_id) are unaffected
- Tests: replace flow version assignment, delete version constraints,
older-version content re-upload allowed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add created_by field to streams for versioning metadata
Add `created_by` FK to `users` table on `streams`, populated automatically
on creation via `UserModifiedBehavior`. Migration also adds unique index on
`(object_id, version)` and renames to `StreamsVersioningFields`.
* fix: address PR review comments on stream versioning
- Add allowMethod(['post', 'patch']) in StreamsController::uploadNewVersion()
- Catch QueryException (SQLSTATE 23000) on SaveEntityAction to return 409
on concurrent version conflicts (unique index already enforces this at DB level)
- Avoid loading entire request body into a PHP string; use hash_update_stream()
on the detached resource for sha1 duplicate detection, then pass the resource
directly as contents to SaveEntityAction
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: include tests and StreamsTable changes in stream versioning
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: correctly buffer request body to avoid PHP string copy and handle non-seekable streams
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: naming conventions for unique indexes
* fix: restore thumbnail fixture accidentally deleted
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update filesystem setup and teardown in StreamsControllerTest
* test: improve coverage for UploadComponent and StreamsTable
- Add testUploadReplaceLatestVersionNotFound: PATCH with no existing streams → 404
- Add testUploadReplaceLatestVersionPrivateUrl: PATCH with ?private_url=true
- Add testBeforeSaveNotNewAutoVersion: auto-assigns version when linking stream to object
- Add testBeforeSaveNotNewObjectEntity: derives object_id from embedded object association
- Add PATCH 'Replace latest version' entry to Postman collection
* refactor: simplify stream upload logic by removing unnecessary try-catch block
* chore: cs
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Niki Corradetti <niki.corradetti@atlasconsulting.it>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Stefano Rosanelli <stefano.rosanelli@gmail.com>