Apply rector's Laravel13 set:
- 12 models: $incrementing/$timestamps properties ->
#[WithoutIncrementing] / #[WithoutTimestamps] attributes
- 7 console commands: $signature property -> #[Signature(...)] attribute
Mechanical changes via 'vendor/bin/rector process'. No behavior changes.
All gates green: pint, phpstan, rector dry-run clean post-fix.
Out-of-scope to the route-forge change; landed separately so the
RouteForge PR diff stays focused on the new feature.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Migrated console command declarations to modern PHP attribute-based
signatures for improved code organization.
* Updated application models to use modern PHP attributes for
configuration instead of traditional properties while preserving all
existing functionality.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2220?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/releases">axios's
releases</a>.</em></p>
<blockquote>
<h2>v1.15.2</h2>
<p>This release delivers prototype-pollution hardening for the Node HTTP
adapter, adds an opt-in <code>allowedSocketPaths</code> allowlist to
mitigate SSRF via Unix domain sockets, fixes a keep-alive socket memory
leak, and ships supply-chain hardening across CI and security docs.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Prototype Pollution Hardening (HTTP Adapter):</strong>
Hardened the Node HTTP adapter and
<code>resolveConfig</code>/<code>mergeConfig</code>/validator paths to
read only own properties and use null-prototype config objects,
preventing polluted <code>auth</code>, <code>baseURL</code>,
<code>socketPath</code>, <code>beforeRedirect</code>, and
<code>insecureHTTPParser</code> from influencing requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a></strong>)</li>
<li><strong>SSRF via <code>socketPath</code>:</strong> Rejects
non-string <code>socketPath</code> values and adds an opt-in
<code>allowedSocketPaths</code> config option to restrict permitted Unix
domain socket paths, returning <code>AxiosError</code>
<code>ERR_BAD_OPTION_VALUE</code> on mismatch. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
<li><strong>Supply-chain Hardening:</strong> Added <code>.npmrc</code>
with <code>ignore-scripts=true</code>, lockfile lint CI, non-blocking
reproducible build diff, scoped CODEOWNERS, expanded
<code>SECURITY.md</code>/<code>THREATMODEL.md</code> with provenance
verification (<code>npm audit signatures</code>), 60-day resolution
policy, and maintainer incident-response runbook. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong><code>allowedSocketPaths</code> Config Option:</strong> New
request config option (and TypeScript types) to allowlist Unix domain
socket paths used by the Node http adapter; backwards compatible when
unset. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>Keep-alive Socket Memory Leak:</strong> Installs a single
per-socket <code>error</code> listener tracking the active request via
<code>kAxiosSocketListener</code>/<code>kAxiosCurrentReq</code>,
eliminating per-request listener accumulation,
<code>MaxListenersExceededWarning</code>, and linear heap growth under
concurrent or long-running keep-alive workloads (fixes <a
href="https://redirect.github.com/axios/axios/issues/10780">#10780</a>).
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a></strong>)</li>
</ul>
<h2>🔧 Maintenance & Chores</h2>
<ul>
<li><strong>Changelog:</strong> Updated <code>CHANGELOG.md</code> with
v1.15.1 release notes. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a></strong>)</li>
</ul>
<p><a
href="https://github.com/axios/axios/compare/v1.15.1...v1.15.2">Full
Changelog</a></p>
<h2>v1.15.1</h2>
<p>This release ships a coordinated set of security hardening fixes
across headers, body/redirect limits, multipart handling, and
XSRF/prototype-pollution vectors, alongside a broad sweep of bug fixes,
test migrations, and threat-model documentation updates.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Header Injection Hardening:</strong> Tightened validation
and sanitisation across request header construction to close the
header-injection attack surface. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10749">#10749</a></strong>)</li>
<li><strong>CRLF Stripping in Multipart Headers:</strong> Correctly
strips CR/LF from multipart header values to prevent injection via field
names and filenames. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10758">#10758</a></strong>)</li>
<li><strong>Prototype Pollution / Auth Bypass:</strong> Replaced unsafe
<code>in</code> checks with <code>hasOwnProperty</code> to prevent
authentication bypass via prototype pollution on config objects, with
additional regression tests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10761">#10761</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10760">#10760</a></strong>)</li>
<li><strong><code>withXSRFToken</code> Truthy Bypass:</strong>
Short-circuits on any truthy non-boolean value, so an ambiguous config
no longer silently leaks the XSRF token cross-origin. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a></strong>)</li>
<li><strong><code>maxBodyLength</code> With Zero Redirects:</strong>
Enforces <code>maxBodyLength</code> even when <code>maxRedirects</code>
is set to <code>0</code>, closing a bypass path for oversized request
bodies. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10753">#10753</a></strong>)</li>
<li><strong>Streamed Response <code>maxContentLength</code>
Bypass:</strong> Applies <code>maxContentLength</code> to streamed
responses that previously bypassed the cap. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10754">#10754</a></strong>)</li>
<li><strong>Follow-up CVE Completion:</strong> Completes an earlier
incomplete CVE fix to fully close the regression window. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10755">#10755</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>AI-Based Docs Translations:</strong> Initial scaffold for
AI-assisted translations of the documentation site. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10705">#10705</a></strong>)</li>
<li><strong><code>Location</code> Request Header Type:</strong> Adds
<code>Location</code> to <code>CommonRequestHeadersList</code> for
accurate typing of redirect-aware requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7528">#7528</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>FormData Handling:</strong> Removes
<code>Content-Type</code> when no boundary is present on
<code>FormData</code> fetch requests, supports multi-select fields,
cancels <code>request.body</code> instead of the source stream on fetch
abort, and fixes a recursion bug in form-data serialisation. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7314">#7314</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10676">#10676</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10702">#10702</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10726">#10726</a></strong>)</li>
<li><strong>HTTP Adapter:</strong> Handles socket-only request errors
without leaking keep-alive listeners. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10576">#10576</a></strong>)</li>
<li><strong>Progress Events:</strong> Clamps <code>loaded</code> to
<code>total</code> for computable upload/download progress events.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/7458">#7458</a></strong>)</li>
<li><strong>Types:</strong> Aligns <code>runWhen</code> type with the
runtime behaviour in <code>InterceptorManager</code> and makes response
header keys case-insensitive. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7529">#7529</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10677">#10677</a></strong>)</li>
<li><strong><code>buildFullPath</code>:</strong> Uses strict equality in
the base/relative URL check. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7252">#7252</a></strong>)</li>
<li><strong><code>AxiosURLSearchParams</code> Regex:</strong> Improves
the regex used for param serialisation to avoid edge-case mismatches.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10736">#10736</a></strong>)</li>
<li><strong>Resilient Value Parsing:</strong> Parses out header/config
values instead of throwing on malformed input. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10687">#10687</a></strong>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/blob/v1.x/CHANGELOG.md">axios's
changelog</a>.</em></p>
<blockquote>
<h2>v1.15.2 - April 21, 2026</h2>
<p>This release delivers prototype-pollution hardening for the Node HTTP
adapter, adds an opt-in <code>allowedSocketPaths</code> allowlist to
mitigate SSRF via Unix domain sockets, fixes a keep-alive socket memory
leak, and ships supply-chain hardening across CI and security docs.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Prototype Pollution Hardening (HTTP Adapter):</strong>
Hardened the Node HTTP adapter and
<code>resolveConfig</code>/<code>mergeConfig</code>/validator paths to
read only own properties and use null-prototype config objects,
preventing polluted <code>auth</code>, <code>baseURL</code>,
<code>socketPath</code>, <code>beforeRedirect</code>, and
<code>insecureHTTPParser</code> from influencing requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a></strong>)</li>
<li><strong>SSRF via <code>socketPath</code>:</strong> Rejects
non-string <code>socketPath</code> values and adds an opt-in
<code>allowedSocketPaths</code> config option to restrict permitted Unix
domain socket paths, returning <code>AxiosError</code>
<code>ERR_BAD_OPTION_VALUE</code> on mismatch. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
<li><strong>Supply-chain Hardening:</strong> Added <code>.npmrc</code>
with <code>ignore-scripts=true</code>, lockfile lint CI, non-blocking
reproducible build diff, scoped CODEOWNERS, expanded
<code>SECURITY.md</code>/<code>THREATMODEL.md</code> with provenance
verification (<code>npm audit signatures</code>), 60-day resolution
policy, and maintainer incident-response runbook. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong><code>allowedSocketPaths</code> Config Option:</strong> New
request config option (and TypeScript types) to allowlist Unix domain
socket paths used by the Node http adapter; backwards compatible when
unset. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>Keep-alive Socket Memory Leak:</strong> Installs a single
per-socket <code>error</code> listener tracking the active request via
<code>kAxiosSocketListener</code>/<code>kAxiosCurrentReq</code>,
eliminating per-request listener accumulation,
<code>MaxListenersExceededWarning</code>, and linear heap growth under
concurrent or long-running keep-alive workloads (fixes <a
href="https://redirect.github.com/axios/axios/issues/10780">#10780</a>).
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a></strong>)</li>
</ul>
<h2>🔧 Maintenance & Chores</h2>
<ul>
<li><strong>Changelog:</strong> Updated <code>CHANGELOG.md</code> with
v1.15.1 release notes. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a></strong>)</li>
</ul>
<p><a
href="https://github.com/axios/axios/compare/v1.15.1...v1.15.2">Full
Changelog</a></p>
<hr />
<h2>v1.15.1 - April 19, 2026</h2>
<p>This release ships a coordinated set of security hardening fixes
across headers, body/redirect limits, multipart handling, and
XSRF/prototype-pollution vectors, alongside a broad sweep of bug fixes,
test migrations, and threat-model documentation updates.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li>
<p><strong>Header Injection Hardening:</strong> Tightened validation and
sanitisation across request header construction to close the
header-injection attack surface. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10749">#10749</a></strong>)</p>
</li>
<li>
<p><strong>CRLF Stripping in Multipart Headers:</strong> Correctly
strips CR/LF from multipart header values to prevent injection via field
names and filenames. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10758">#10758</a></strong>)</p>
</li>
<li>
<p><strong>Prototype Pollution / Auth Bypass:</strong> Replaced unsafe
<code>in</code> checks with <code>hasOwnProperty</code> to prevent
authentication bypass via prototype pollution on config objects, with
additional regression tests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10761">#10761</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10760">#10760</a></strong>)</p>
</li>
<li>
<p><strong><code>withXSRFToken</code> Truthy Bypass:</strong>
Short-circuits on any truthy non-boolean value, so an ambiguous config
no longer silently leaks the XSRF token cross-origin. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a></strong>)</p>
</li>
<li>
<p><strong><code>maxBodyLength</code> With Zero Redirects:</strong>
Enforces <code>maxBodyLength</code> even when <code>maxRedirects</code>
is set to <code>0</code>, closing a bypass path for oversized request
bodies. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10753">#10753</a></strong>)</p>
</li>
<li>
<p><strong>Streamed Response <code>maxContentLength</code>
Bypass:</strong> Applies <code>maxContentLength</code> to streamed
responses that previously bypassed the cap. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10754">#10754</a></strong>)</p>
</li>
<li>
<p><strong>Follow-up CVE Completion:</strong> Completes an earlier
incomplete CVE fix to fully close the regression window. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10755">#10755</a></strong>)</p>
</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>AI-Based Docs Translations:</strong> Initial scaffold for
AI-assisted translations of the documentation site. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10705">#10705</a></strong>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="582934382e"><code>5829343</code></a>
chore(release): prepare release 1.15.2 (<a
href="https://redirect.github.com/axios/axios/issues/10789">#10789</a>)</li>
<li><a
href="4709a48fa2"><code>4709a48</code></a>
fix: added fix for memory leak in sockets (<a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a>)</li>
<li><a
href="be3336014e"><code>be33360</code></a>
chore: update changelog (<a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a>)</li>
<li><a
href="4791514466"><code>4791514</code></a>
fix: more header pollutions (<a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a>)</li>
<li><a
href="6feafcff6c"><code>6feafcf</code></a>
fix: socket issue (<a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a>)</li>
<li><a
href="302e2739c6"><code>302e273</code></a>
docs: update docs, add a couple actions etc (<a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a>)</li>
<li><a
href="ac42446be5"><code>ac42446</code></a>
chore(release): prepare release 1.15.1 (<a
href="https://redirect.github.com/axios/axios/issues/10767">#10767</a>)</li>
<li><a
href="908f2206b6"><code>908f220</code></a>
docs: update threatmodel (<a
href="https://redirect.github.com/axios/axios/issues/10765">#10765</a>)</li>
<li><a
href="f93f815525"><code>f93f815</code></a>
docs: added docs around potential decompressions bomb (<a
href="https://redirect.github.com/axios/axios/issues/10763">#10763</a>)</li>
<li><a
href="1728aa1b15"><code>1728aa1</code></a>
fix: short-circuits on any truthy non-boolean in withXSRFToken (<a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/axios/axios/compare/v1.15.0...v1.15.2">compare
view</a></li>
</ul>
</details>
<br />
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Consolidated aircraft/subfleet access into model-driven authorization
and moved fare override resolution to API responses for more consistent,
permission-aware results.
* **Tests**
* Added feature and unit tests validating subfleet/aircraft access rules
and enforcing query-count bounds on hot paths.
* **Chores**
* Added database indexes to improve query performance for
aircraft/subfleet and type-rating lookups.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2217?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Make FlightResource the single source of truth for the time keys in
the API response instead of relying on Eloquent's default cast
serialization + the absence of \$hidden. Adds explicit
\$res['departure_time'] / \$res['arrival_time'] projections (H:i:s
strings) alongside the existing \$res['dpt_time'] / \$res['arr_time']
(Hi strings) aliases.
Stale comment also fixed -- previously said the structured columns
were kept out of the response via Flight::\$hidden, which is no longer
true after that array was removed in the prior commit.
Behavior unchanged in practice (the cast already emitted the same
string), but the resource is now self-documenting and decoupled from
future changes to Flight::\$hidden.
Remove `$hidden = ['departure_time', 'arrival_time']` from the Flight
model. The structured TIME columns are public surface and should be
queryable / inspectable via the model's default serialization, not
hidden behind the resource-only `dpt_time` / `arr_time` projection.
API surface (now)
- `departure_time` / `arrival_time`: emitted by Eloquent as `H:i:s`
strings via the datetime:H:i:s cast.
- `dpt_time` / `arr_time`: still projected by FlightResource as legacy
`Hi` strings for backward-compat consumers.
Tests
- FlightResourceTimeFieldsTest now asserts presence of all four keys.
- FlightListShapeTest's assertJsonStructure adds the four time keys
to lock the API contract.
All gates green; affected suites: 97 tests pass.
Reverses the modern Attribute-method approach: the Flight model no
longer carries dptTime/arrTime accessors. Instead, the canonical
columns are `departure_time` / `arrival_time` (Carbon, datetime:H:i:s
cast); the legacy `dpt_time` / `arr_time` `Hi`-formatted strings are
projected onto the JSON payload only by FlightResource at API response
time.
Why
- Public Attribute methods on a model are queryable surface and shouldn't
be hidden behind protected visibility just to satisfy Larastan's
modelAppends rule. Removing the methods (and the matching `$appends`
entries) sidesteps the rule entirely.
- Keeps the API contract stable (`dpt_time`/`arr_time` still appear in
responses, `departure_time`/`arrival_time` stay hidden) while
internal code paths use the structured columns directly.
Model
- Removed `dptTime()` / `arrTime()` Attribute methods from Flight.
- Removed `dpt_time` / `arr_time` from `$appends`.
- Kept `departure_time` / `arrival_time` in `$hidden` (matches the
contract locked by FlightResourceTimeFieldsTest).
- Dropped the now-unused `FlightTimeParser` import.
API resource
- FlightResource::toArray() now formats `dpt_time` / `arr_time` from
the Carbon columns at response time. BidFlightResource inherits the
projection via parent::toArray().
Form / factory
- FlightForm: TimePicker now binds to `departure_time` / `arrival_time`.
- FlightFactory: default state generates `departure_time` /
`arrival_time` instead of the legacy keys.
Import / export plumbing
- Filament FlightImporter: kept the `dpt_time` / `arr_time` CSV column
headers for backward compat; `fillRecordUsing` now parses via
FlightTimeParser and writes through setAttribute('departure_time'/
'arrival_time') to satisfy PHPStan's strict property typing.
- Filament FlightExporter: same CSV headers, `state()` callbacks
format `Hi` from the structured columns.
- Service FlightImporter: transforms legacy keys to structured keys
in import() before firstOrNew mass-assign.
- Service FlightExporter: overrides the array keys after the column
loop so CSV columns continue to use the legacy header names with
Carbon-derived values.
- LegacyImporter (old phpvms import): parses source `deptime`/`arrtime`
into `departure_time`/`arrival_time` directly.
Tests
- Deleted tests/Unit/Models/FlightTimeAccessorTest.php (covered behavior
that no longer exists on the model).
- FlightResourceTimeFieldsTest now seeds via `departure_time` /
`arrival_time` factory state; assertions on the projected
`dpt_time` / `arr_time` keys remain.
- ImporterTest: end-of-import assertions read
`$flight->departure_time->format('Hi')` instead of `$flight->dpt_time`.
All four gates green; affected suites: 104 tests pass.
Convert legacy getDptTimeAttribute / setDptTimeAttribute /
getArrTimeAttribute / setArrTimeAttribute methods on the Flight
model to the modern Illuminate\Database\Eloquent\Casts\Attribute
return style (matches existing days() accessor at line 318).
Each pair collapses into a single protected method returning
Attribute::make(get: ..., set: ...) where the set closure returns
an array mapping the backing column (departure_time / arrival_time)
to the parsed value via FlightTimeParser::parse.
Notes
- Visibility must be protected, not public: Larastan's
rules.modelAppends rule only detects virtual accessors on
protected Attribute methods. With public it fails with
'Property dpt_time does not exist in model.'
- Departure_time / arrival_time docblocks corrected from
string|null to Carbon|null to match the datetime:H:i:s cast at
Flight::casts(). Without this, PHPStan flags ?->format('Hi') as
method.nonObject.
All four gates remain green.
Sweep of CodeRabbit + arthurpar06 review items on PR #2215
(feat/route-bundles).
Bug fixes
- FlightsRelationManager: gate OldExport/OldImport actions on
!config('phpvms.use_queued_filament_imports') so they don't render
alongside the queued Filament Import/Export actions.
- FlightTimeBackfiller: move \$parsed++ into each per-field success
branch. Counters were row-vs-field unit mismatched, making the
completion log misleading (parsed=rows but failures=fields).
- FlightTimeBackfillerTest: replace Log::shouldReceive('warning')
->zeroOrMoreTimes() with ->once()->with(...) and Mockery::on(...)
payload matcher; the old expectation asserted nothing. Cast id to
int in the matcher (SQLite returns string IDs).
- FlightListShapeTest: assert both 'enabled' and 'active' keys in
the API contract structure (resource emits both as backward-compat
alias; previously only 'active' was locked in).
H1 — native AsEnumCollection for route_types
- Migration ..._add_capability_columns_to_subfleets_table.php: change
route_types from string(64) to json(). Portable across MySQL,
PostgreSQL and SQLite. Column is new in this PR so no data
migration needed.
- Subfleet model: cast switches from custom FlightTypesCast to
AsEnumCollection::of(FlightType::class).
- Delete app/Casts/FlightTypesCast.php and its unit test.
- Test updates explicitly document the behavior shift: empty selection
now stored as JSON [] rather than collapsed to null. Custom
behaviors lost: auto-dedupe, auto-sort, log+drop on invalid token.
None had consumers; future business logic can decide on the
null-vs-empty semantic.
H9 — split backfill into data migration
- Schema migration ..._add_time_columns_to_flights_table.php no
longer runs the backfill inline; just creates the columns.
- New database/migrations_data/2026_05_19_000000_backfill_flight_times.php
calls FlightTimeBackfiller::run() and is picked up by
MigrationService::runAllDataMigrations() on next admin update.
H2 — command rename to phpvms: prefix
- PreserveHiddenVisibility signature: flights:preserve-hidden-visibility
→ phpvms:preserve-hidden-visibility for consistency with the rest
of the namespace (phpvms:dev-install, :importer, :email-test,
:version).
- Updated refs in console test, docs/UPGRADING.md, and openspec
change-tracking docs (tasks, design, proposal).
H10 — composer scripts
- Add --parallel flag to 'pint' and 'pint:test' so AGENTS.md's
'composer pint --test' command uses multi-threading.
H5 — native badge for FlightForm status entry
- Refactor status_badge TextEntry from manual HtmlString + raw <span>
markup to TextEntry::badge()->color()->state(). Removes the need
for ->html(), manual e() escaping, and hardcoded Tailwind classes;
inherits Filament theming and dark-mode automatically.
R2-N — defense in depth
- Wrap :url substitution in e() in parentBundleOwnedDatesMessage().
FlightBundleResource::getUrl() is safe-by-construction but the
translation places :url in HTML context.
Collateral
- pint --dirty added an explicit ': array' return type to
FlightResource::toArray() (it had only @return array in PHPDoc).
BidFlightResource::toArray() needed the same change for covariance;
PHPStan would otherwise fail with method.childReturnType.
Prior session items (also included in this commit since not yet
committed):
- FlightTypesCast wrote validation + invalid-token logging.
- FlightsTable bundle-disabled badge with eager-loaded bundle.
- FlightImporter (Filament) added exists:flight_bundles,id rule.
- FlightImporter (Service) memoized default bundle id to avoid N+1.
- FlightForm: minDate as closure that skips for edit; fixed static
cache leak by using Laravel's container instead.
- SetVisibleFlights: DB::table → Flight::query() for ORM consistency.
- FlightBundle: #[Scope] attribute on visible scope; hasDates() as
has_dates Attribute accessor.
- FlightBundleTest, SubfleetCapabilityTest, FlightsBulkActionsTest
updated for new behaviors.
- ShieldSeeder dead-code cleanup.
All four pre-PR gates green: pint --test, phpstan, rector --dry-run,
pest on affected paths (180 tests passed).
Filament\Forms\Components\Placeholder is @deprecated in Filament 5
(see vendor/filament/forms/src/Components/Placeholder.php — extends
TextEntry, ->content() just calls ->state()).
Migrate both FlightForm sites:
- bundle_dates_message: HtmlString anchor for parent-bundle-owns-dates
- status_badge: HtmlString colored badge for flight status
TextEntry auto-detects Htmlable state via CanFormatState::formatState
(vendor/filament/.../Concerns/CanFormatState.php:387-389), blade
unescapes via <?= ... ?>. Added explicit ->html() to both for clarity
+ safety.
Extract the 2026_05_19_add_time_columns migration's inline chunk loop
into app/Support/FlightTimeBackfiller::run() so the parse+update+log
behaviour can be tested directly. Migration now calls the service.
New tests/Feature/Support/FlightTimeBackfillerTest.php covers:
- happy path: 3 parseable dpt_time formats (0800/08:00/8am) → 08:00:00
- unparseable input: 'not a time' → departure_time NULL, failures=1
- idempotency: rows with departure_time already set are skipped
Tests bypass Flight factory + insert via DB::table() because the
Flight mutator writes to departure_time, not the legacy dpt_time
column the backfill reads. Log facade asserted via shouldReceive
('warning')->zeroOrMoreTimes() — return-value (parsed/failures)
proves the warning code path executed. Tightening to exact message
match was flaky due to Faker-internal Log::channel chain noise.
Filament 5's ParentResourceRegistration auto-derives inverse rel name
from camelCase(classBasename(parentModel)) → 'flightBundle'. The
Flight model defines bundle() as the actual relation, so the original
patch added flightBundle() as a defensive alias.
Cleaner: override getParentResourceRegistration() to call
inverseRelationship('bundle'). Removes model-side dead code; keeps
bundle() as the sole BelongsTo. Behaviour identical:
nested CreateFlight + EditFlight still bind parent via
$flight->bundle() instead of $flight->flightBundle().
Create page has 4 visibility/content closures that each invoke
resolveParentBundle() → FlightBundle::find($value). Without memoization
that's 4 identical queries per form render. Add request-scoped static
cache; collapses to 1 query.
Cache holds null results too (array_key_exists vs isset) so a missing
bundle isn't re-queried. Form is short-lived per request → no explicit
invalidation needed. Record-based path (edit page) unchanged.
8.2 FlightForm::resolveParentBundle — drop 3 speculative route param
names (bundle, flightBundle, record), keep only canonical flight_bundle.
Verified via route:list — admin/flights/{flight_bundle}/flight/...
8.5 FlightsNavigationTest — replace ReflectionClass with Filament public
API (getParentResource). Added getParentResourceRegistration assertion
to prove nav suppression (HasNavigation::registerNavigationItems skips
when parent registration present). shouldRegisterNavigation stays true
by default — wrong API for this check.
8.8 composer.json — register pint + pint:test scripts so AGENTS.md
pre-PR checklist works.
* fix(installer): handle missing DB and fix CSRF middleware class
- InstalledCheck: wrap Schema::hasTable in try-catch to handle when
database is unreachable or does not exist, preventing crash before
installer can redirect
- SystemPanelProvider, AdminPanelProvider: fix non-existent
PreventRequestForgery class to ValidateCsrfToken (Laravel 12)
- .env.example: change default SESSION_DRIVER from database to file
so fresh installs don't require sessions table before migrations run
* Filament installer/system styles and changes
* Don't include frontend js in system/admin
* Don't include frontend js in system/admin
* fix(translations): normalize 'informations' and snake_case keys
- Fix ungrammatical plural 'informations' → 'Information' across all locales
- Rename joined-word translation keys to snake_case convention:
auth: create_account, forgot_password, full_name, email_address, etc.
pireps: new_flight_report, flight_time, flight_level, fields_readonly, etc.
common: newest_pilots, live_map, rights_reserved, toggle_colors
flights: flight_time, profile: transfer_hours, widgets: live_map
dashboard: total_hours, your_balance, your_last_report, no_reports_yet
- Update all PHP/Blade references to renamed keys
- Add missing translation keys to non-English locales (send_reset_link,
comment, error_changing_state, fields, field_value, landing_rate, etc.)
- Fix fr/filament.php: two keys on single line
- Fix en/aircraft.php: double space in 'Dry Weight'
- Run Pint on all modified files
* Design and other small changes
* feat(admin): add News Filament resource and seed PIREP sample data
Replace the dashboard News widget with a full Filament resource that
opens Create/Edit in modals (no separate pages routed). Adds the
matching policy so filament-shield can wire permissions.
Add a dev seed script (scripts/seed_sample_pireps.php) that inserts
sample airports, subfleets, aircraft, and 10 admin-owned PIREPs (last
two PENDING) to populate an empty database for UI testing.
Fix lazy-load exception on the PIREPs admin table by eager-loading
airline, aircraft and user via modifyQueryUsing; the table only worked
before because the database had no PIREPs.
* chore(admin): pending Filament panel refactor in working tree
Pre-existing uncommitted work from earlier sessions. Not authored or
reviewed in this commit's session; batched here only to clear the
working tree.
Includes:
- Airports resource simplified to modal-via-no-route pattern
(CreateAirport, EditAirport page stubs deleted; getPages trimmed
to index only)
- Modules page renamed to Addons
- New plugins: ClearCachesPlugin, SidebarCollapseTogglePlugin
- ClearCaches Livewire component
- Filament admin theme.css restyle
- NavigationGroup enum + lang/common, lang/filament tweaks
- system/brand.blade.php moved to shared/brand.blade.php
- Override attribute normalization across Filament resources
* fix(review): address CodeRabbit PR #2211 review feedback
Critical
- Restore CSRF middleware (PreventRequestForgery) in Admin + System panels
- Fix Addons page redirectRoute (modules -> addons) in 3 actions
- SeederService: fix SplFileInfo TypeError, guard missing seed dir, arrow fn
- YamlSeeder: propagate $ignore_errors, fix SplFileInfo bug, guard file read,
proper File facade import
- Installer: gate install via withoutGlobalScopes()->exists() not count(),
drop duplicated stream() of full buffer after chunked streaming
- .env.example: APP_DEBUG/DEBUGBAR_ENABLED default to false
Bugs
- seed_sample_pireps.php: swap KSAN/KLAX lat/lon
- theme.css: drop phantom mobile ::before rule (no matching selector)
- module-links-topbar.blade: localize hardcoded Admin label
- seven/app.blade: drop @vite call referencing removed bundles
i18n
- de/auth: fix Adresse typo + translate send_reset_link
- de/installer.title: brand wordmark phpvms
- fr/auth.toc_accept: fix acceptez typo
- es-es/it/tr/jp/pt-br: translate 10 PIREP keys + send_reset_link/fill_captcha
- de/es-es/it/jp/tr/common: add toggle_colors
Cleanup
- Remove dead commented blocks (Installer headerActions, InstallSeeder,
YamlSeeder, seed_sample_pireps section dividers)
- NewsResource label uses common.news namespace
- login-hero alt uses lowercase brand
Rector + Pint auto-fixes
- Add declare(strict_types=1) where missing
- Add arrow-fn return types for brandLogo + News table schema
- Standardize catch-block exception names
* wip: pre-plan baseline (pirep map work + admin/frontend JS split)
* feat(pireps): add PerformanceChartService for ACARS chart datasets
* feat(pireps): load fares/field_values + build performance chart payload
* refactor(pireps): drop relation manager tabs (will embed inline)
* feat(pireps): register Chart.js wrapper for performance charts
* feat(pireps): add detail page header + stat strip partial
* feat(pireps): add unified Route & Performance card partial
* feat(pireps): add notes + filed route partial
* feat(pireps): add detail sidebar partial
* feat(pireps): wire new detail blade composition + embedded RMs
* feat(pireps): style v2 detail page (Stripe-flavored, theme tokens, dark mode)
* chore(pireps): drop legacy .fi-pirep-modal-* styles
* fix(pireps): inline Acars query to satisfy PHPStan level 5
larastan does not forward #[Scope] attribute methods through
HasMany relation builders, so calling `->ofType()->orderedByCreatedAt()`
on the relation tripped method.notFound at PHPStan level 5.
Replace with explicit where() + orderBy() — same runtime behavior,
no scope-forwarding surface for the analyzer to miss.
* fix(pireps): prune wasted eager loads + preserve zero-fuel samples
- Eager-load `user.rank` (sidebar reads $pilot->rank->name and would N+1
on every detail page view).
- Drop `comments.user`, `transactions`, `fares.fare` from loadMissing —
the embedded relation managers issue their own queries, so preloading
at the page level only adds round-trips for data the page itself never
uses.
- Fix fuel series to treat a fuel value of `0` as a real sample rather
than null. Previously `$s->fuel ? ... : null` collapsed legitimate
empty-tanks readings into gaps.
* fix(pireps): restore eager loads for embedded relation managers
Filament relation managers ARE separate Livewire queries, but their
blade columns still reach into nested relations on each row
(e.g. PirepFare->fare in FaresRelationManager). With lazy loading
disabled globally via Model::preventLazyLoading, those accesses
hard-fail with LazyLoadingViolationException.
Restore comments.user / transactions / fares.fare to the parent
loadMissing(). Keep user.rank from the earlier fix.
Reverts the wasted-load optimization in d6a8a3b5 — the optimization
was based on speculation, not measurement, and broke real
runtime behavior. Net effect: one extra preload per detail page,
versus a 500 error.
Refs: detail.blade.php:46 @livewire(FaresRelationManager)
* fix(pireps): address review findings (eager loads, ts null guard, downsample tail)
Tier A fixes from post-merge review of the redesign branch.
ViewPirep::mount
- Add fares.pirep + field_values.pirep to loadMissing. The FaresRelationManager
and FieldValuesRelationManager column `disabled` closures call
`$record->pirep->read_only` per row. Under Model::preventLazyLoading
(active in dev/test) that triggered a lazy-load violation when editing
any fare/field value. Production silently lazy-loaded on every edit.
- Drop airline from loadMissing. No partial or relation manager touches the
airline relation; the preload was dead weight.
PerformanceChartService::ts
- Guard against null Acars$created_at (documented Carbon|null; partial imports
leave it unset). Use \DateTimeInterface instanceof check rather than ?->,
because larastan narrows the model property to non-null Carbon and reports
nullsafe.neverNull at level 5 even when the runtime type is genuinely null.
PerformanceChartService::downsample
- Always include the final sample. Previously `$i % $step === 0` could drop
the last point when total count was not a multiple of step, hiding the
touchdown / arrival sample from the chart. Index 0 stays included via
the existing modulo, so first + last are both preserved.
* refactor(pireps): drop route text card, restore route bar under map
Removes the raw waypoint route card from the PIREP view (notes partial),
restores the route bar (departure/arrival ICAO + name + block times + line)
that sits below the map in route-performance partial, and re-adds its
matching CSS in the admin theme.
- resources/views/filament/pireps/detail/notes.blade.php: remove route text card
- resources/views/filament/pireps/detail/route-performance.blade.php: restore route bar block + php helpers (blockOff/blockOn/duration/unitDistance)
- resources/css/filament/admin/theme.css: restore .fi-pirep-detail-v2-route-bar styles
* wip(pireps): landing analysis card, perf chart split, comment policy + tooling
Bundles in-progress PIREP detail work plus build tooling that landed in the
working tree:
PIREP detail
- Landing analysis card: runway plan-views, scorecard polar, attitude
indicators (route-performance partial owns the markup; see prior commit).
- PerformanceChartService: build landing payload + extra chart series.
- Sidebar + modal/detail partials: stacked custom-fields card, layout tweaks.
- PirepCommentPolicy + RelationManager wiring for per-row authorization.
Assets
- Split AlpineComponents into resources/js/components/{pirep-landing-analysis,pirep-performance-chart}.js.
- bin/build.js esbuild driver; package.json/vite.config.js/.gitignore updates.
- Drop legacy resources/js/admin/pirep-performance-chart.js (moved to components/).
Misc
- AdminPanelProvider, base_map.js, config/services.php small adjustments.
- justfile task runner.
- composer/package lock updates.
* feat(admin): improve PIREP detail page layout and theming
- Split fares/transactions into separate cards (was single Finance card)
- Fares: plain text columns, no pagination, currency formatting
- Transactions: JetBrains Mono money amounts, no pagination
- Net total row in its own card
- Sidebar facts (Pilot/Flight) use stacked layout matching PIREP fields
- JetBrains Mono font for all money values (fi-pirep-money-mono)
- Map switches CartoDB.Voyager/DarkMatter on theme change
- Admin sidebar width 14.5rem
- PIREP detail sidebar sticky positioning accounts for topbar
* View pirep page updates
* fix: admin pirep page UX improvements and chart stability
- View PIREP button: transparent background, no fill
- Flight number links to view PIREP page
- PIREP detail heading shows flight number + route (VMS7620 C.PF MMMX→MMAA)
- Hero section: pilot name + aircraft on top line, filed time + source on secondary
- Sidebar: remove sticky scroll, let entire page scroll naturally
- Performance chart: fix canvas detachment on tab switch by wrapping in wire:ignore parent and using .canvas
* Shape up finances table
* feat: add flight log timeline tab with phase-colored badges
Two-column flight strip on Flight Log tab rendering ACARS LOG entries
(type=2) with timestamp/altitude/speed badges and phase assignment
from PerformanceChartService phase detection. Sort toggle flips
between earliest-first and latest-first order.
* fix(review): address code review blockers
- phpunit.xml: restore DB_CONNECTION=sqlite (suite was hitting MySQL
with :memory: database name, all tests 500ing on first query)
- ViewPirep::getHeading: use typed $this->record over getRecord()
to fix 4 phpstan property.notFound errors at level 5
- NewsResource: drop user_id overwrite on EditAction; preserved the
original author. user_id only set on create now.
- PerformanceChartService: remove discarded toFloat call on landing
speed (no consumer anywhere in codebase)
Pre-PR checks: pint v, phpstan v, rector v, pest v (368 passed).
* chore(review): tidy review minors
- finance widgets: refresh /livewire/update comment to v4 hashed path
format; widget visibility logic is path-agnostic so behavior unchanged
- seed_sample_pireps: make bulk PIREP loop idempotent. lookup by
source_name + flight_number + airport pair so re-running the script
no longer duplicates the 10 routes on each invocation
* fix(pireps): flight-log rows respect dark mode
Inline style="background: #..." on each row + badge was overriding
the dark-mode CSS rules, leaving alternating rows white in dark mode.
- blade: drop the phaseColor/phaseBg/phaseTextColor/rowBg PHP closures.
collapse to a single $phaseBucket helper that maps a phase code to a
bucket name (ground/climb/cruise/descent/land/neutral). row + badge
pick up CSS modifier classes instead of inline styles.
- theme.css: add zebra + phase tint rules for the row (.--zebra,
.--phase-climb, .--phase-land) plus six badge variants. each has a
light + dark pair using translucent tints with brighter text so the
badge stays readable against the gray-900 row background.
* fix(ci): guard Vite::asset() against missing build manifest
composer install runs `package:discover` which boots service providers.
AdminPanelProvider::register() called Vite::asset() eagerly, throwing
ViteManifestNotFoundException on CI / fresh clones where composer runs
before npm has built the frontend.
Move the admin maps Js asset behind an `is_file(public_path(...))`
guard so the provider registers the rest of the assets unconditionally
and skips the Vite-resolved entry until the manifest exists. The map
blade has its own @vite() at render time so end-user behavior is
unchanged once `npm run build` has run.
Verified locally by removing public/build/manifest.json and running
composer dump-autoload — package:discover now completes cleanly.
* fix(ci): guard AlpineComponent assets against missing dist files
filament:upgrade (post-autoload-dump) was throwing on CI when copying
resources/js/dist/components/pirep-performance-chart.js to the public
assets folder. The esbuild'd Alpine components are produced by
bin/build.js and not committed, so a fresh clone has no dist directory
when composer post-autoload hooks run.
Loop the components through an `is_file()` guard like the Vite manifest
check added in the previous commit. Provider registration completes
when the frontend hasn't been built; the components register the
moment `npm run build` (or `node bin/build.js`) creates the files.
Verified by removing resources/js/dist and re-running
`php artisan filament:upgrade` — clean.
* chore(ci): build frontend before composer + commit dist artifacts
Two coordinated fixes for CI ordering + asset shipping:
(A) Reorder CI so Node + frontend build run BEFORE composer install.
The Laravel post-autoload-dump hook chain (package:discover ->
filament:upgrade) boots service providers which read the Vite manifest
and copy AlpineComponent files at register time. Running composer first
threw ViteManifestNotFoundException and copy() ENOENT on a fresh clone.
Affects all three jobs in build.yml:
- `build` (matrix x3 PHP versions): Node setup moved above composer
- `artifacts` (release packager): same swap
- `docker` (image builder): Node setup added (was composer-only)
(C) Commit `resources/js/dist/components/*.js` esbuild output. Removes
the runtime dependency on `node bin/build.js` for downstream consumers
(shared hosting installs, fresh composer-create-project flows). Matches
the existing convention for `public/js/filament/*` and
`public/assets/frontend/js/*` which are already committed pre-built.
A "Verify committed dist files match a fresh build" CI step diffs the
post-build tree against HEAD so contributors who edit the AlpineComponent
sources without re-running `npm run build:components` fail the build with
a clear error rather than shipping stale dist files.
The defensive `is_file()` guards in AdminPanelProvider stay (committed
in 7fdf2b2b + 97618bb8) — belt and suspenders against partial checkouts.
Refs PR #2211 review.
* chore(js): reorganize Filament components under admin/ + lint cleanup
CI lint failures surfaced two issues that local lint had buried under
1318 warnings from the committed esbuild output (Chart.js internals):
- resources/js/app.js was an empty stub (`//`), never imported anywhere.
Removed.
- RW_RUNWAY_TOP in pirep-landing-analysis.js was declared but never
used. Removed.
Folded in a directory reorg while touching the same files:
- Sources move to resources/js/admin/components/. Mirrors the existing
resources/js/admin/ tree (request.js, storage.js, maps/, etc.) which
was created in this branch for the admin-only JS split.
- Esbuild output mirrors the source layout — moves to
resources/js/dist/admin/components/.
- bin/build.js entry + outfile paths updated.
- AdminPanelProvider resource_path() updated to match.
Tooling guards added so dist noise stops drowning real warnings:
- .oxlintrc.json: ignore resources/js/dist/** (bundled output)
- .oxfmtrc.json: ignore resources/js/dist/** (minified output)
- bin/build.js itself was new in this branch and not yet oxfmt-clean;
reformatted (quotes/semis/indent).
Verified locally: oxlint 0 warnings 0 errors, fmt:check clean for all
tracked files, pint v, phpstan v, rector v.
* fix(ci): three-step bootstrap to break composer<->vite chicken-and-egg
The previous "npm before composer" reorder broke the build:
resources/css/filament/admin/theme.css imports
vendor/filament/filament/resources/css/theme.css, which doesn't exist
until composer install has run. Vite resolve failed at CI line 1.
Reverting to "composer before npm" re-triggers the original failure
(ViteManifestNotFoundException during package:discover; ENOENT during
filament:upgrade) because composer post-autoload-dump boots service
providers that need the Vite manifest + dist files.
Fix: three-step bootstrap, applied to all three jobs in build.yml.
1. composer install --no-scripts
Pulls vendor/ (Vite needs it for theme.css @imports) WITHOUT
firing post-autoload-dump (which would boot the providers
prematurely).
2. npm install + npm run build
Now resolves the vendor CSS @import. Produces
public/build/manifest.json and rebuilds
resources/js/dist/admin/components/*.js.
3. composer dump-autoload
Re-fires post-autoload-dump. package:discover and
filament:upgrade boot providers cleanly — the Vite manifest
and dist files are both present.
Verified locally inside the dev container: removed vendor/,
public/build/, resources/js/dist/, ran the three steps, all clean.
The freshness check (git diff --exit-code resources/js/dist) and the
build matrix's lint/fmt/pint/pest/phpstan stages run unchanged — only
the bootstrap order in front of them changes.
* fix(installer): address CodeRabbit review feedback
- Add Response return type to InstalledCheck::handle()
- Translate hero_title for es-es, fr, pt-br locales
- Standardize brand alt text to 'phpvms'
- Null-safe source label in PIREP row blade to avoid error
when source_name is set but source enum is null
- Render landing_rate stat box for 0 values (use !== null check)
* chore(build): stop committing compiled JS/CSS assets
Untrack frontend build artifacts and rely on CI to (re)build them:
- resources/js/dist/ AlpineComponents (output of bin/build.js)
- public/js/filament/, public/css/filament/, public/fonts/filament/
- public/css/filament-spatie-backup/
The Filament assets are re-published by 'php artisan filament:upgrade'
during composer post-autoload-dump. The AlpineComponents are produced by
'npm run build'. Both already run in build.yml and release.yml.
Also drop the now-obsolete 'verify committed dist files match a fresh
build' step in CI \u2014 dist files are no longer tracked.
* refactor(filament): remove is_file guards around admin assets
The guards were added when composer post-autoload-dump could fire before
`npm run build` produced the frontend artifacts. CI now does a three-step
bootstrap (composer install --no-scripts -> npm build -> composer
dump-autoload) so dist files and the Vite manifest always exist by the
time AdminPanelProvider::register() runs. Release tarballs ship them
pre-built.
If assets are missing at runtime we want to know immediately, not
silently degrade the admin panel.
* refactor(filament): mirror Pireps namespace under views/filament/pireps
Co-locate page views and partials under resources/views/filament/pireps/
to mirror App\Filament\Resources\Pireps namespace.
- pages/{list-pireps,view-pirep}.blade.php (was filament/resources/pireps/pages/)
- partials/row.blade.php + partials/detail/* (was filament/pireps/{row,detail}/)
- Update $view in ListPireps and ViewPirep
- Rewrite 6 @include paths
* chore(build): add npm clean script to remove built assets
Removes public/build (Vite output) and resources/js/dist (esbuild
components output) in one command.
* Add the fillament public assets back in
* Move the vite js into the page render hook, lazily load map on first use
* wip
* refactor: update enum handling and improve type hints across models
* refactor: deprecate labels and select methods in HasSelect trait for better separation of concerns
* copilot suggestions
* refactor: Casts, Observers and Traits
* refactor: replace Sluggable observer with HasSlug trait in models
* test: add HasSlug trait tests for slug generation and uniqueness
* refactor: rename resource classes for consistency and update references
* refactor: update return type of index method to use AnonymousResourceCollection
* refactor: simplify slug generation in HasSlug trait and update tests for consistency
* refactor: update namespace annotation in UserBidResource for clarity
* apply coderabbit suggestions
* fix tests
* chore: remove prettus/l5-repository (Phase 8 cleanup)
Phase 8 of the Prettus repository removal. Phases 1-7 deleted all 25 concrete
repositories and rewired callers; this drops the package itself plus the
remaining scaffolding.
- Migrate Sluggable observer registration from ObserverServiceProviders boot
to #[ObservedBy(Sluggable::class)] attributes on FlightField, FlightFieldValue,
Page, PirepField, PirepFieldValue.
- Delete app/Providers/ObserverServiceProviders.php, app/Contracts/Repository.php,
app/Repositories/ (incl. Criteria/WhereCriteria.php), config/repository.php.
- Drop RepositoryServiceProvider and ObserverServiceProviders from config/app.php.
- Sweep dead 'use Prettus\Validator\Exceptions\ValidatorException' imports from
10 files plus the 12 orphaned '@throws ValidatorException' docblock lines that
PHPStan would otherwise flag as throws.notThrowable.
- Move pagination default from deleted config/repository.php to config/phpvms.php
('pagination.limit' => 20, matches historical value). Update 8 prod callers +
2 test sites to read 'phpvms.pagination.limit'.
- Drop App\Contracts\Repository exception from tests/Arch/GlobalTest.php
http-helpers rule.
- composer remove prettus/l5-repository (drops prettus/laravel-validation
transitively).
No public API, JSON shape, or behavior change. Pint, PHPStan level 5, Pest, and
Rector --dry-run all pass; the 7 pre-existing CSRF/ProfileUpdated test failures
on main remain unchanged.
* fix(pagination): default 50, max cap 100, central paginate_limit() helper
Address PR #2202 review feedback (CodeRabbit + Copilot):
- config/phpvms.php: pagination.limit = 50 (default page size),
pagination.max = 100 (hard cap on ?limit= query input).
- Add paginate_limit() helper in app/helpers.php that resolves the raw
?limit= value, falls back to pagination.limit, and clamps to
[1, pagination.max]. Single source of truth for per-page sanitization.
- Replace ad-hoc `$request->query('limit') ?: config(...)` patterns in
8 controllers with paginate_limit():
- Api: AirportController, FleetController, FlightController,
NewsController, UserController (fleet + pireps)
- Frontend: FlightController, PirepController, UserController
Frontend/UserController previously used 20 as fallback default; now
consistent with the rest of the app at 50.
- SearchAirportsRequest validator now bounds ?limit= against
pagination.max (100) instead of pagination.limit (50), so callers can
request the full clamp range. Add boundary test asserting limit=100
passes and limit=101 fails.
Net effect: API endpoints can no longer be coerced into oversized result
sets via ?limit=. Default page size moves from 20 to 50 to match the
review consensus.
* feat(journal): add JournalService, JournalTransactionQuery, model methods
Move balance math onto Journal model as instance methods returning Money:
- recalculateBalance(): self
- getCreditBalanceBetween(Carbon, ?Carbon, ?string): Money
- getDebitBalanceBetween(Carbon, ?Carbon, ?string): Money
Convert $casts to casts() method.
Add JournalService for writes (post, deleteAllForObject) — drops the
dead try/catch around ValidatorException; deleteAllForObject iterates so
JournalTransactionObserver fires (preserves cached balance).
Add JournalTransactionQuery as plain class for ref-model lookups
(replaces JournalRepository::getAllForObject). No FormRequest — no HTTP
endpoint drives it; arguments are object/journal/date.
Modernize observers with #[ObservedBy] on Journal and JournalTransaction;
remove imperative ::observe registrations from ObserverServiceProviders
to prevent double-firing.
Phase 7 setup commit. Callers still use JournalRepository; migration
follows in next commit.
* refactor(journal): migrate callers to JournalService and Journal model
FinanceService, PirepFinanceService, Api/PirepController, and the nightly
RecalculateBalances cron now use JournalService::post/deleteAllForObject,
Journal::recalculateBalance, and JournalTransactionQuery::build directly.
JournalService::post takes Eloquent\Model (not App\Contracts\Model) so
User references keep working without a wrapper. Drops dead ValidatorException
import and stale @throws docblocks now that Prettus is out of the call path.
The Phase 0 deleteAllForObject characterization test moves to
tests/Unit/JournalServiceTest.php with assertions intact.
* feat(repos): remove JournalRepository (Phase 7)
Last journal repository deleted. Reads now go through Journal model
(balance math) and JournalTransactionQuery (transaction lookups);
writes go through JournalService.
Drops the phpstan ignoreErrors entry that masked Repository magic-method
calls — no real repos remain in app/Repositories/, only Criteria/.
* fix(journal,finance): address latent bugs found in Phase 7 review
PirepService::delete now drops the pirep's journal entries before the
forceDelete. The polymorphic ref_model_id has no FK constraint, so
without explicit cleanup those rows would dangle — still summed by
nightly recalculate, skewing journal balances. Inject PirepFinanceService
to call deleteFinancesForPirep first.
JournalService::post now uses $journal->currency (with the system
setting as fallback) for the transaction.currency column. The legacy
behavior unconditionally stamped the system setting regardless of the
target journal's currency, so a non-default-currency journal could
silently log transactions in the wrong currency.
JournalTransaction::casts() had a typo: 'credits' (plural, no such
column) instead of 'credit'. Result: $tx->credit came back uncast
(string from MySQL bigint). Mostly masked by PHP coercion and
SUM() returning numeric, but a real risk for any code doing direct
arithmetic on $tx->credit.
JournalTransactionObserver renamed saved → created so that updates to
a JournalTransaction no longer re-add its credit/debit to the cached
journal balance. saved fires on both insert and update; created fires
only on insert.
PirepFinanceService::processFinancesForPirep wraps the delete + 9 pay*
calls in DB::transaction. A partial failure mid-pay would otherwise
leave the journal half-cleared with new entries on top.
JournalTransactionQuery::build replaces the broken
where('post_date', '=', Y-m-d) with whereDate. The legacy '=' compare
against a datetime column would silently match nothing for any row
not posted at midnight UTC. No production caller passes $date today,
so this is a dead-code fix — but the code is now correct.
Adds clarifying docblock notes on Journal::recalculateBalance (sums
unfiltered by post_date — disagrees with getCurrentBalance when
future-dated rows exist) and Journal::getCreditBalanceBetween
(whereDate day-precision vs getCreditBalanceOn datetime-precision).
* fix(journal,finance): address PR review feedback
CRITICAL
- JournalTransaction tags cast was 'array' but post() stores as
CSV string. JSON decode of CSV returned null on read. Switch
cast to 'string' to match storage format.
MAJOR
- PirepController finances_get / finances_recalculate now use
findOrFail so a missing id yields 404 instead of a 500 type
error from JournalTransactionQuery::build's non-nullable param.
- PirepService::delete is now wrapped in DB::transaction so
partial-delete failures roll back. Same pattern as
processFinancesForPirep.
- FinanceTest 'pirep expenses nightly' assertion was tautological
(toHaveCount on the 3-key array shape always passes). The test
also lacked initJournal calls and queried by ref_model=Airline
even though processExpenses posts with ref_model=Expense. Fixed
by calling initJournal and counting transactions on each
airline's journal directly.
MINOR
- JournalTransactionQuery copy()'s the Carbon argument before
setTimezone so callers don't see their instance mutated.
- Reverted the whereDate change for post_date (column is DATE,
not DATETIME, so the legacy = compare was correct and uses the
index; whereDate disables it).
- Switched JournalTransactionQuery to getKey() over ->id so the
read path matches JournalService::deleteAllForObject.
NITPICKS
- RecalculateBalances cron uses chunkById(500) instead of all().
- JournalService::deleteAllForObject uses lazyById for memory
safety on large cleanups.
- JournalService gets declare(strict_types=1).
- processFinancesForPirep declares its Pirep return type.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Switched many backend lookups to use centralized model-driven queries;
flight searching rebuilt with a new query flow for richer filtering,
multi-column sorting, and optional pagination.
* **Bug Fixes**
* Missing-record lookups now consistently return not-found responses;
diversion handling reliably reuses or creates reposition flights;
airline/flight selection lists and visible flight-type filtering
improved.
* **Tests**
* Added extensive unit and feature tests covering search, bids,
finances, ACARS, diversion, and related behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Phase 6 of prettus repo removal. Deletes PirepRepository +
AcarsRepository.
All callers migrated to direct Eloquent, Pirep::scopeActiveFlights, or
new App\Queries\PirepSearchQuery. 15 commits, behavior preserved.
API JSON shape pinned by existing PirepListShapeTest, unchanged.
What moved where:
- AcarsRepository::getPositions -> Pirep::scopeActiveFlights (model
scope, 4 callers)
- Api/UserController::pireps logic -> PirepSearchQuery +
SearchPirepsRequest (mirrors Phase 4)
- AcarsRepository::forPirep -> inlined at 3 GeoService sites
- PirepService::update callers -> direct $pirep->update->refresh
- pirepRepo->find/findWithoutFail -> Pirep::find or findOrFail per
pre-existing guard
- Dead methods dropped: getPending, getPendingCount, getAllAcarsPoints
15 files, both repo files deleted.
## Test plan
- pest --parallel --filter='Pirep|Acars|PirepListShape' -> 48 passed
(402 assertions)
- AcarsTest 20/180, PirepListShapeTest 1/86
- pint clean. PHPStan clean on Phase 6 files
(2 pre-existing KvpService errors on main, unrelated)
## Review fixes included
Last 3 commits address review feedback:
- fix: wire @sortablelink params (kyslik emits ?sort/?direction, form
request now aliases)
- fix: wire status filter into Api/UserController::pireps
- chore: drop dead 'like' branch + stale @throws docblock
## Pre-existing main bugs found, track separately
1. Single-process Pest broken (in-memory DB lifecycle). Workaround:
--parallel
2. KvpService.php PHPStan errors at lines 47, 58 (Spatie Valuestore
type)
3. Local public/build/manifest.json missing -> 9 Blade tests 500
4. Phase 4 SearchUsersRequest has same kyslik sort mismatch (back-port
the fix)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a typed PIREP search request and enhanced search/query parsing
(field:value and free-text) with validated ordering and limits.
* **Refactor**
* Switched live flights, map, widgets, controllers and services to
direct model queries and a shared "active flights" query for more
consistent live tracking, paging, and ordering.
* **Chores**
* Minor docblock cleanups.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
PirepSearchQuery::applySearch carried a defensive 'like' branch
suppressed via @phpstan-ignore. FIELD_SEARCH only contains '='
operators, so the branch was unreachable. Reshape FIELD_SEARCH to
list<string> + drop the branch. YAGNI: 1-line revert if a future
field needs LIKE.
Frontend/PirepController::index no longer throws RepositoryException
(removed with prettus). Drop the @throws docblock; pint nukes the
now-unused import.
SearchPirepsRequest validates ?status= as a documented filter, but
the controller only branched on ?state=. Clients passing ?status=
got their filter silently ignored. Wire it through.
@sortablelink in pireps/table.blade.php emits ?sort=&direction=, but
PirepSearchQuery reads ?orderBy=&sortedBy=. Column header sort was
silently broken on /pireps. Add prepareForValidation() that aliases
sort/direction onto the canonical params before rules run.
Also add flight_time to ORDERABLE_FIELDS so the column header at
table.blade.php:11 actually validates.