Add admin:MediaMoveStorageLocalToCloud and admin:MediaMoveStorageCloudToLocal:
- Copy media (+thumbnail) between local and cloud disks, verify by size (and
sha256 against original_sha256 when present) before deleting the source.
- Integrated GC: delete the verified source copy (local on upload, cloud on
download), set version=4 / reset to 3, and bust MediaService/StatusService
caches. --keep-local / --keep-cloud opt out.
- Manage PF_ENABLE_CLOUD in .env AND the live runtime + config cache so new
uploads route to the correct backend mid-migration on a hot server. Uses the
installer's atomic .env writer (shared ManagesMediaStorageEnv trait).
- --limit / --dry-run / --force.
Replaces media:migrate2cloud (CloudMediaMigrate) and media:s3gc
(MediaS3GarbageCollector); scheduler now runs MediaMoveStorageLocalToCloud
hourly for straggler upload + GC. Keeps media:fix-nonlocal-driver.
Adds feature tests (download+GC, --keep-cloud, dry-run, env-flag flip both
directions, unknown-disk guard).
Prepares for Laravel 13 where VerifyCsrfToken is deprecated in favor
of PreventRequestForgery. The old class remains as an alias in v13 but
will be removed in a future version.
Adds a global rate limiter (240 req/min per user or IP) to all API
routes. Previously rate limiting was only applied ad-hoc on individual
routes, leaving some endpoints unprotected.
Laravel Passport 13 renamed:
- CheckScopes → CheckToken (verifies ALL listed scopes)
- CheckForAnyScope → CheckTokenForAnyScope (verifies ANY listed scope)
The old class names no longer exist, causing BindingResolutionException
on all /api/v1/admin/* routes that use the 'scope' or 'scopes' middleware
aliases.
Delete 4 middleware wrapper classes that added no custom logic:
- EncryptCookies (empty $except)
- TrimStrings ($except matches framework default)
- VerifyCsrfToken (exceptions moved to validateCsrfTokens() in bootstrap)
- TrustProxies (headers matched framework default)
CSRF exceptions (/api/v1/*, oauth/token) are now configured via
$middleware->validateCsrfTokens(except: [...]) in bootstrap/app.php.
All 107 tests pass.