Commit Graph

708 Commits

Author SHA1 Message Date
Nabeel Shahzad
2c7f4f5cc9
chore(routeforge): rector NewlineAfterStatement fixes
Two cosmetic blank-line-after-continue fixes flagged by
'vendor/bin/rector --dry-run' on the route-forge change:

- app/Services/RouteForge/Rules/L6OriginEqualsDestination.php
- app/Http/Controllers/Admin/RouteForgeController.php (decorate loop)

Section 9 quality gates green:
- tsc strict: clean
- oxlint (34 files / 93 rules): 0 warnings, 0 errors
- oxfmt: all formatted
- vite build: 113 modules, routeforge entry 26.69 KB gzip
  (Decision 16 budget 60 KB -> 33.31 KB headroom)
- pint --dirty: passed
- phpstan level 5: no errors
- rector dry-run scoped to these 2 files: clean post-fix

Out-of-scope rector findings (19 legacy Laravel-13 attribute migrations
across app/Models/*.php and app/Console/Commands/*.php) shipped on a
separate branch (rector-modernize-legacy).
2026-05-24 11:55:36 -05:00
Nabeel Shahzad
370c2f6fdf
feat(routeforge): admin batch flight composer + lint pipeline
New Filament page at /admin/route-forge: compose dozens of scheduled
flights across five topologies (Hub→Spokes, Spokes→Hub, Hub & Spokes,
Mesh, Chain), preview rows live, lint for operational issues, commit
as one Flight Bundle in a single atomic transaction.

Frontend: TypeScript + Preact 10 + @preact/signals + @date-fns/tz.
Separate Vite entry, 26.69 KB gzip (well under the 60 KB target).
Client-side generator + 12 lint rules; localStorage draft resume.

Backend: RouteForge service + 12 PHP lint rule classes + duplicate
checker + commit orchestration (Flight::withoutEvents + single
bundle-level activity log). Six endpoints under
/admin/route-forge/api/, gated by permission:create:flight.

Bundle picker is dual-mode — type a new name to create, or pick an
existing bundle to append flights to (read-only summary, server skips
the bundle persist step and stamps the existing id onto new flights).

Airport picker: click-to-open dropdown with 50-result alpha pages,
checkbox rows for multi-select, prefix-only search via new
searchMode=prefix opt-in on the shared AirportSearchQueryV1
(/api/airports default substring behavior preserved).

Quality: tsc strict mode clean, oxlint 0/0, phpstan no errors,
pint passing. Pest scaffolding deferred per workflow.

Tracking: openspec/changes/route-forge — 66/106 tasks complete
(sections 1, 2, 4, 5, 6, 7, 8; Pest 4.6/5.3/6.4 + sections 9–12
remaining).

# Conflicts:
#	package-lock.json
#	package.json
2026-05-24 11:55:33 -05:00
Arthur Pariente
dcc25a108f
Merge branch 'main' into refactor-subfleets-filtering 2026-05-23 22:35:50 +02:00
Nabeel Shahzad
28f49b97c3
feat(schema-modernization): foundations for RouteForge
Schema, model, Filament, cron, and visibility-semantics foundations
required by the RouteForge change. Four phases delivered as one
coherent change. Verified against four spec files (flight-bundles,
flight-time-storage, subfleet-capability, flight-visibility).

Phase 1.1 \xe2\x80\x94 Flight time columns
- flights.departure_time, flights.arrival_time TIME NULL
- Flight model accessors (Hi-format) + mutators (FlightTimeParser)
- FlightTimeParser supports 9 formats + Z/L/tz suffix stripping
- Migration backfills inline via chunked raw DB::table->update()
- Legacy dpt_time/arr_time VARCHAR columns preserved for one release

Phase 1.2 \xe2\x80\x94 Subfleet capability columns
- subfleets.cruise_speed, max_range_nm, route_types (CSV VARCHAR(64))
- FlightTypesCast splits/sorts/dedupes/logs-on-invalid
- routeforge defaults in config/phpvms.php
- SubfleetForm Operational Capability section

Phase 1.3 \xe2\x80\x94 Flight Bundles
- flight_bundles table + flights.bundle_id NOT NULL FK
- Default bundle seeded inline; existing flights backfilled
- FlightBundle model + factory + BundleObserver (queued recompute)
- FlightBundleResource as sole Flights nav entry (slug=flights,
  icon=OutlinedMap, sort=2)
- Nested FlightResource under FlightBundleResource (slug=flight,
  parentResource, create+edit only, no nav entry)
- FlightsRelationManager with relatedResource for full-page row actions
- FlightForm: bundle selector removed (route-bound), date inputs
  hidden when parent bundle owns dates, XSS-safe placeholder link
- Date columns stored as TIMESTAMP UTC, UI converts to local tz
- Soft-deleting a bundle leaves children non-deleted; visibility
  recompute sets visible=false (no cascade delete)
- Spatie Shield permissions: flight_bundle.* seeded

Phase 1.4 \xe2\x80\x94 Visibility semantics
- flights.active renamed to flights.enabled (admin source of truth)
- flights.visible is cron-managed combined state
- SetActiveFlights cron deleted; SetVisibleFlights cron added
- Two-pass bulk SQL UPDATE scoped by bundle, chunks of 500
- RecomputeBundleVisibility queued job dispatched on bundle save/restore
- Flight::scopeVisible added; scopeActive retained as plain alias
- Pilot-facing read sites use ->visible() (AirportController fix)
- FlightResource JSON + FlightExporter CSV expose both enabled+active
- Four-state status badge (Disabled / Disabled by Bundle / Enabled
  Out of Window / Enabled & In Window) replaces Visible toggle
- 5 visibility indexes added

Verification
- 469 Pest tests passed (2058 assertions)
- phpstan level 5 clean (646 files)
- pint --test clean
- rector --dry-run clean
- All four spec files PASS (28 requirements + 36 scenarios verified
  by kimi-spec-reviewer)

Migration path documented in docs/UPGRADING.md and openspec/changes/
schema-modernization-for-routeforge/.
2026-05-23 12:05:09 -05:00
Arthur Pariente
8caf34420b
refactor(FlightService, PirepService, UserService): replace UserService usage with direct user methods for subfleet and aircraft access 2026-05-23 10:58:36 +02:00
Arthur Parienté
7490e72523
[8.x] refactor: use native php enum instead of class (#2210)
* 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
2026-05-12 16:29:41 -05:00
Arthur Parienté
7de823a0a7
[8.x] refactor: migrate to the new modern laravel structure (#2207)
* refactor: migrate to the new modern laravel structure

* fix .env.test
2026-05-07 14:58:11 -05:00
Arthur Parienté
992b6d343a
[8.x] feature: upgrade to Laravel 13 (#2204)
* upgrade to laravel 13

* phpstan

* rollback to symfony 7.4

* fix: ModuleService merge

* refactor: new rector rules
2026-05-06 11:27:21 -05:00
Arthur Parienté
7c9b446262
[8.x] Refactor models and tests for slug handling and resource consistency (#2191)
* 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
2026-05-03 11:38:30 -05:00
Nabeel S.
849ab9ac7f
chore: remove prettus/l5-repository (Phase 8 cleanup) (#2202)
* 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.
2026-05-02 14:39:06 -05:00
Nabeel S.
368034f7ef
Refactor/phase-7-journal (#2200)
* 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.
2026-04-29 14:13:45 -05:00
Nabeel S.
62d63a3f64
Refactor/phase 6 flight airline (#2197)
<!-- 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 -->
2026-04-29 09:56:19 -05:00
Nabeel Shahzad
bfe75ee870 chore(pirep): drop dead 'like' branch + stale @throws docblock
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.
2026-04-27 17:04:45 -05:00
Nabeel Shahzad
5ae3026bb1 fix(pirep): wire status filter into Api/UserController::pireps
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.
2026-04-27 17:04:06 -05:00
Nabeel Shahzad
80be8da6c3 refactor(pirep): migrate Dashboard, Api/Pirep, Api/Acars controllers to Eloquent 2026-04-27 16:53:16 -05:00
Nabeel Shahzad
e20844a999 refactor(pirep): migrate Frontend/PirepController to direct Eloquent + PirepSearchQuery 2026-04-27 16:51:12 -05:00
Nabeel Shahzad
e5b917bd9b refactor(pirep): migrate Api/UserController::pireps to PirepSearchQuery 2026-04-27 16:48:51 -05:00
Nabeel S.
d601b1f9e2
Phase 5 of prettus repository removal. (#2195)
* test(setting): characterization tests for setting() helper + API shape

* refactor(setting,expense): add #[Scope] + #[ObservedBy] + casts() + drop dead $rules

* feat(services): add SettingService with cache-aware store/retrieve

* feat(finance): add FinanceService::getExpensesForType

* refactor(setting): migrate setting()/setting_save() helpers to SettingService

* refactor(setting): migrate Api controller, Filament page, importer, test helper to SettingService

* refactor(expense): migrate PirepFinanceService, ExportAction, FinanceTest to FinanceService::getExpensesForType

* refactor(setting,expense): delete SettingRepository and ExpenseRepository

* test(setting): use Setting::byKey scope in characterization test

* refactor(setting): drop redundant Cache::forget in Maintenance page

SettingService::store() now invalidates the per-key cache slot internally
(introduced in d9c94195). Manual Cache::forget calls after setting_save()
became dead code in this branch, mirroring the same cleanup applied to
Filament/Pages/Settings.php in 31aa55da. Drops the now-unused Cache import
as well — caught by deep review.

* fix(setting): increment count in SettingsImporter run

Pre-existing bug. $count was declared but never incremented, so the
import log always read 'Imported 0 settings' regardless of how many
settings the legacy importer actually wrote.

Found during Phase 5 deep review.

* refactor(filament): wrap settings save in DB::transaction

The Filament Settings page writes N setting rows in a foreach loop,
then calls FinanceService::changeJournalCurrencies() which rewrites
journal + journal_transaction rows to a (potentially) new currency.
A throw mid-loop or mid-currency-migration left the DB with half-saved
settings or a half-migrated journal.

Wrap both halves in a single DB::transaction() so a partial failure
rolls back all writes atomically.

Found during Phase 5 deep review.

* refactor(services): declare strict_types=1 across app/Services/

Phase 5 introduced declare(strict_types=1); in SettingService.php only.
This commit applies the same declaration to the remaining 65 service
files for consistency.

65 files migrated. 6 files required minimal signature/cast adjustments
to handle existing caller patterns where strict mode exposed implicit
type coercion at runtime or in phpstan:

- FareService::recalculateFares cast $pivot->capacity to (float)
  before floor() (DB returns string).
- Finance/RecurringFinanceService::processExpenses cast
  $expense->ref_model to (string) before explode() (nullable column).
- AirportLookup/VaCentralLookup, GeoService: pass $e->getMessage()
  to Log::error() instead of the Exception object.
- DatabaseService::time() cast Carbon to (string) (return type contract).
- ModuleService::installModule pass $file->getRealPath() to PharData
  and Madzipper::make() (UploadedFile -> string path).

No files reverted. No tests modified.

Found during Phase 5 deep review (convention drift).

* style(setting): align match arms for pint 1.29.1

Pint 1.29.1 aligns => arrows in match expressions under
binary_operator_spaces (with align_single_space_minimal). 1.29.0 did
not. CI installs latest globally and fails on the un-aligned form.

Bumped composer.lock so local matches CI.

* Update app/Services/Finance/RecurringFinanceService.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* refactor(installer): add strict types to LoggerTrait

LoggerTrait declared strict_types=1 but methods had no parameter or
return types. Added: comment(string $text): void, info(string $text):
void, error(string $text): void.

FlightImporter:59 was passing a Throwable to error() — switched to
$e->getMessage() to match the new contract. Same pattern applied in
5b2133a8 to VaCentralLookup and GeoService.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-27 15:21:27 -05:00
Nabeel Shahzad
ac8e9e02c8
refactor(user): address PR #2194 review feedback
Fix issues raised by code review on Phase 4:

- UserController::index now honors validated `?limit` query param via
  paginate($request->integer('limit') ?: config('repository.pagination.limit', 15)),
  matching the convention used in NewsController, FleetController, AirportController.
- UserSearchQuery::applySearch joins multi-pair field-specific search with OR
  (matching the legacy Prettus RequestCriteria default and the documented behavior
  in SearchUsersRequest's PHPDoc), and falls back to free-text when a colon-prefixed
  payload contains no allowlisted fields (e.g. "8:30") instead of silently returning
  all users.
- Role::byName docblock corrected: case-sensitivity follows the database collation
  (case-sensitive on SQLite, case-insensitive on MySQL utf8mb4_unicode_ci) rather
  than being a guarantee of the scope itself.
- UserService::getUserFields PHPDoc rewritten to describe the actual three-valued
  contract: true=public-only, false=private-only, null=all visibility-allowed.
- Trivial scope tests removed: deleted RoleScopesTest entirely (both cases were
  smoke tests of where() with no real signal, one was even mis-named) and trimmed
  UserScopesTest to keep only the composition test that proves #[Scope] chaining
  works. The single-where scope tests are tested implicitly by feature tests.
- New UserSearchQueryTest cases lock the OR-join and free-text-fallback behaviors.
2026-04-26 16:42:29 -05:00
Nabeel Shahzad
606c484c6e
fix(user): preserve findOrFail contract in FlightController::bids
Code review caught: User::find() returns null on missing user, then
foreach ($user->bids) NPEs to 500. Old Prettus userRepo->find() called
findOrFail under the hood, throwing ModelNotFoundException for clean 404.

Restore old contract via User::findOrFail().
2026-04-26 16:23:24 -05:00
Nabeel Shahzad
315ce8d58d
refactor(user): migrate Api/User, FlightController, LatestPilots widget, Filament to direct Eloquent 2026-04-26 16:14:41 -05:00
Nabeel Shahzad
3961a67b97
refactor(user): absorb getUserFields into UserService + migrate ProfileController 2026-04-26 16:05:04 -05:00
Nabeel Shahzad
4a0e2a7b5a
refactor(user): migrate Frontend/UserController to UserSearchQuery 2026-04-26 15:51:14 -05:00
Nabeel Shahzad
2d59d97459
fix(airport): validate limits and preload options 2026-04-25 21:21:43 -05:00
Nabeel Shahzad
ef94e03f30
fix(airport): restore legacy search behavior and harden test bootstrap 2026-04-25 14:54:04 -05:00
Nabeel Shahzad
235cdb13f3 refactor(airport): delete AirportRepository
- Frontend/AirportController: drop airportRepo, use
  Airport::with('files')->find($id) ($id was already uppercased
  earlier in the method)
- Frontend/PirepController: drop airportRepo from constructor
  (only reference was a commented-out selectBoxList call). Phase
  2 precedent (Aircraft was the same case).
- Filament/Actions/ExportAction: AIRPORT case uses Airport::all()
- Delete app/Repositories/AirportRepository.php (dead selectBoxList
  goes with it)
- phpstan.neon: suppress larastan.relationExistence for
  FilesTrait::files() across Aircraft/Airline/Airport/Subfleet.
  The morphMany is trait-defined and works at runtime, but
  larastan can't statically resolve trait-declared polymorphic
  relations. First direct Eloquent with('files') call lands in
  this commit, hence the new suppression.

Final commit of Phase 3. AirportRepository is gone; 9 repositories
remain (was 10 at branch start). First Query class
(AirportSearchQuery) and search Form Request (SearchAirportsRequest)
are now in production, ready to be reused by Phases 4/6/7.
2026-04-25 12:57:38 -05:00
Nabeel Shahzad
22c39e8766 refactor(airport): migrate Api/AirportController to AirportSearchQuery
- index() and search() use AirportSearchQuery + SearchAirportsRequest
- index_hubs() uses Airport::byHub()->orderByIcao()
- get(Airport $airport) uses route model binding (case-insensitive
  via Airport::resolveRouteBinding)
- routes/api.php: rename airports/{id} -> airports/{airport} so
  Laravel binds to the typed parameter (URL format unchanged)
- Drop AirportRepository injection and Prettus criteria use
- Match Phase 2's perPage() pattern for ?limit= handling
- Add regression test asserting lowercase ICAO routes resolve

Public API contract preserved: ?search=field:value syntax, ?hub=,
?hubs=, ?limit=, ?orderBy=, ?sortedBy= all continue to work.
Drops un-tested Prettus magic params (?with, ?withCount, ?filter,
?searchFields, ?searchJoin) — narrows the public contract to what's
actually exercised.
2026-04-25 12:33:43 -05:00
Nabeel Shahzad
64a615789f
refactor: Phase 2 — Fleet domain (Aircraft, Subfleet, Navdata)
Phase 2 of the prettus/l5-repository removal. Deletes 3 Fleet-domain
repositories and migrates their callers to direct Eloquent. Introduces
the first #[ObservedBy] attribute migrations on Aircraft and Subfleet.

## Repositories deleted

- NavdataRepository (2 callers, no unique methods)
- AircraftRepository (6 callers, dead selectBoxList() also dropped)
- SubfleetRepository (5 callers, selectBoxList() ported as private
  helper on FlightController — its only caller)

## Caller migrations

- Filament/Actions/ExportAction: inline Aircraft::orderBy()->get(),
  Subfleet::all() (2 cases)
- Api/FleetController: empty constructor; Subfleet::with()->paginate(),
  Aircraft::with()->where()->first(); explicit ?limit= handling
- Api/UserController: Aircraft::find() (findWithoutFail → find);
  passes $perPage explicitly to UserService::getAllowableSubfleets
- Api/NewsController: explicit ?limit= handling restored (was lost in
  Phase 1 when migrating from the Repository contract's paginate()
  override at app/Contracts/Repository.php:112-129)
- Frontend/PirepController: constructor surgery + findOrFail/find swaps
- Frontend/FlightController: constructor surgery; ported subfleetSelectBoxList()
  with null-safe airline access
- Services/PirepService: constructor surgery + Aircraft::find / where
- Services/UserService: constructor surgery; Subfleet::when()->with();
  through() instead of transform() to preserve LengthAwarePaginator
  wrapper when paginating; $perPage parameter to keep request() out
  of services (Tests\Arch\GlobalTest forbids it)
- Services/AirlineService::canDeleteAirline: partial rewire (subfleet
  uses Subfleet::where()->exists(); pirep/flight repos remain for
  their phases)
- Services/FlightService: Navdata::whereIn('id', $route_points)->get()
- Services/GeoService: Navdata::where('id', $route_point)->get();
  dropped dead ModelNotFoundException catch (where()->get() never
  throws that)

## Model modernizations

- Aircraft: drop dead public static array $rules; attach
  #[ObservedBy(AircraftObserver::class)]
- Subfleet: drop dead $rules; convert public $casts property to
  protected casts() method (Laravel 11+ form); attach
  #[ObservedBy(SubfleetObserver::class)]
- ObserverServiceProviders: remove the imperative Aircraft::observe()
  and Subfleet::observe() lines + their now-unused imports. Other
  observer registrations (Airport, Flight, Journal, Setting, User,
  Sluggable) stay until their domain phases.

## Pagination contract preserved

paginate($limit)->appends($request->except(['page', 'user'])) on
all three migrated paginated endpoints (/api/fleet, /api/news,
/api/user/fleet). Mirrors the deleted Repository contract exactly,
including the 'user' exclusion (the test framework's auth middleware
leaks the full User model into request()->query() — without exclusion,
next_page URLs would carry the serialized User as query params).

## New tests

- Tests\Unit\AirlineTest: 'cannot delete airline with subfleet' and
  'can delete airline with no associations' — the rewired branch had
  no direct coverage and the all-clear happy path was untested.
- Tests\Feature\ApiTest: 3 new pagination contract tests for /api/fleet,
  /api/news, and /api/user/fleet — assert meta.per_page matches the
  requested limit AND meta.next_page (the codebase's
  CustomPaginatedResourceResponse moves it there from links.next)
  contains the original ?limit= forward.

## Known trade-off

GeoService::parseRoute previously enjoyed a 5-minute Prettus result
cache via findWhere; the new direct Navdata::where()->get() is
uncached. Acceptable: PK lookup on a small indexed table.

## Verification

- 240 Pest tests pass (1630 assertions); 0 failures
- PHPStan level 5: no errors
- Pint: pass
- 13 → 10 repositories
- 0 remaining references to AircraftRepository, SubfleetRepository,
  or NavdataRepository (one docblock breadcrumb in FlightController
  is intentional documentation)

## Followups

Tracked locally in docs/superpowers/followups.md:
- F2.1: Form Request validation pass on legacy controllers (raised
  by review on PirepController::fares — pre-existing semantic match
  with Prettus's find() throw-on-miss)
- F2.2: perf(filament/export): eager-load relations in CSV exporters
  to eliminate N+1 (raised by review on ExportAction — pre-existing
  in deleted repo code)
2026-04-25 10:21:57 -05:00
Nabeel Shahzad
e726fed381
chore: address PR review feedback from CodeRabbit + Copilot
Applies in-scope cleanups flagged during code review on PR #2190:

- helpers.php: fix stale "KVP repository" docblocks (→ "KVP service");
  rename local $kvpRepo → $kvpService; add ": void" return type and
  correct docblock on kvp_save() (it persists, not reads).
- Api/NewsController::index(): add explicit
  ": AnonymousResourceCollection" return type to replace stale
  "@return mixed" (the method body was rewritten in this PR).
- LegacyImporterService::$kvpRepo: type as KvpService instead of
  mixed — the @var docblock was updated in the rename commit but the
  property type was not.
- NewsService::deleteNews(): switch from News::findOrFail(\$id)->delete()
  to News::whereKey(\$id)->delete() so it stays idempotent, matching
  the null-return style of updateNews(). Matches old repo-layer
  semantics (silent no-op on missing id).
2026-04-24 21:16:33 -05:00
Nabeel Shahzad
625986a0c1
refactor(news): replace NewsRepository with Eloquent direct
- NewsService: direct News::create/find/fill+save/delete calls, no more
  repo dependency
- Api/NewsController: News::with/latest/paginate instead of repo chain
- LatestNews widget: same, using Eloquent latest() and paginate()
- Remove dead public static array $rules from News model (unused by
  anything — was a Prettus validator hook that was never activated)

The Phase 0 NewsListShapeTest locks the JSON contract, so API
consumers see no behavioral difference.

Part of Phase 1b of the Prettus repository removal.
2026-04-24 19:57:07 -05:00
Nabeel Shahzad
15357aa384
refactor(pages): replace PageRepository with bySlug scope + Eloquent direct
Added a #[Scope] bySlug($slug) method to Page model for the common
slug-lookup pattern. PageController and PageLinksComposer updated to
use the scope and direct Eloquent respectively.

Uses Laravel 11+ #[Scope] attribute syntax as per spec.

Part of Phase 1b of the Prettus repository removal.
2026-04-24 19:49:31 -05:00
Nabeel Shahzad
b1b36a8a9d
refactor(pirep-fields): replace PirepFieldRepository with Eloquent direct
Three callers all used plain Eloquent methods (all, whereIn) via the
repo layer. Replaced with direct PirepField::... calls. The repo
had no custom methods.

- PirepController (Frontend): 3 whereIn() call sites
- CreatePirepRequest, UpdatePirepRequest: all() call

Part of Phase 1b of the Prettus repository removal.
2026-04-24 19:22:58 -05:00
Nabeel Shahzad
f3bceae914
fix(news): eager-load user relation in /api/news to avoid N+1
The /api/news index endpoint paginates News records and serializes them
through NewsResource, which dereferences $this->user. Without the
eager load, multi-item responses would lazy-load on each iteration
(now fatal because preventLazyLoading is enabled).

Bumped the response-shape test to count(3) so the eager load is
genuinely exercised; the previous count(1) masked the N+1.
2026-04-24 17:50:13 -05:00
Nabeel Shahzad
f53b113e70
chore: enable Model::preventLazyLoading and fix existing N+1 violations
Enable Laravel's preventLazyLoading in dev/test environments to catch
N+1 query regressions during the upcoming repository refactor phases.

Fixes 3 distinct N+1 patterns revealed by the check (7 failing tests):

- Expense::ref_model: eager-load in ExpenseRepository::getAllForType
  and RecurringFinanceService::processExpenses (also in ImporterTest).
- Flight::airline: eager-load in SetActiveFlights::checkFlights where
  the Flight::ident accessor triggers lazy load via Log::info.
- Pirep::aircraft: add 'aircraft' to the eager loads in
  Api/UserController::pireps so the Pirep Resource doesn't lazy load
  aircraft during response serialization.

Part of Phase 0 for the Prettus repository removal.
2026-04-24 17:50:13 -05:00
Arthur Pariente
e9df3e386b fix(ProfileController): make simbrief_username optional in validation rules and fix password hash 2026-04-24 16:54:54 -05:00
Arthur Pariente
383d435b1f fix(ProfileController): update home_airport_id validation to be nullable 2026-04-24 16:54:54 -05:00
Arthur Pariente
e5c255bd4a fix(ProfileController): improve validation and refactor user data handling 2026-04-24 16:54:54 -05:00
Arthur Parienté
438d1f3ebe
fix(UserController): update error handling for SimBrief username validation (#2174) 2026-03-27 16:16:16 -05:00
Arthur Parienté
379a9b38cc
[8.x] feat: improve tests suite (#2166)
* wip

* fix simbrief tests

* more fixes

* run pint

* fix(tests): fake notifications in registration test

* refactor(tests): AI suggestions and performance improvements

* deprecation warning with PHP85

* fix(tests): fake notifications in access with valid invite test

* refactor(build): optimize autoload and remove unnecessary commands

* refactor(tests): update PHPUnit configuration and improve test structure

* refactor: put GeoJson under the right namespace
2026-03-18 16:35:19 -05:00
Arthur Parienté
06426afe25
[8.x] feat(simbrief): use json format instead of xml (#2149)
* db changes

* allow simbrief username to be updated in profile

* ask for the simbrief username

* update how an ofp is created and retrieved

* first implementation of dto

* update and fix everything

* update tests

* fix phpstan, add caster and refactor namespaces

* fix phpstan

* fix alternate_navlog type

* fake storage for tests

* add an observer to delete the json when deleting the simbrief sql entry

* apply some coderabbit fixes

* more coderabbit fixes

* update DTOs

* make sure all DTOs are final

* fix naming conventions

* make etops and tracks optional

* fix phpstan

* try to fix ci by using sqlite instead of mysql

* Update composer.lock

* move simbrief ofp url to config

* run pint
2026-03-15 10:35:20 -05:00
Arthur Parienté
b2bc7b0900
[8.x] refactor: upgrade pint (#2152)
* refactor: upgrade pint

* exclude modules from pint

* apply ai suggestions
2026-03-14 13:34:18 -05:00
Arthur Parienté
07faf8640a
[8.x] Drop PHP 8.2, add PHP 8.5 support & upgrade core dependencies (#2142)
* remove php82 support

* bump pest to v4

* bump a bunch of packages for PHP85

* upgrade filament and livewire

* replace all the deprecated request::get (symfony 7.4)

* update livewire config file

* update php versions in ci

* fix phpstan

* add a check for the diversion handler
2026-02-03 11:29:44 -06:00
Arthur Parienté
489a68b54e
[8.x] feat(phpstan): support levels 3, 4 and 5 (#2095)
* wip

* feat(phpstan): support level 3

* feat(phpstan): support level 4

* feat(phpstan): support level 5

* fix(phpstan): fix Money constructor

* some fixes after merge

* fix time during tests
2025-10-21 13:05:36 -05:00
Arthur Parienté
56d61b64e9
fix(flights): flights query error with sqlite (#2109)
Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
2025-10-21 11:19:29 -05:00
Arthur Parienté
5c839c5e2a
[8.x] feat(flights): split settings into 'only allow flights from current' and 'only show flights from current' (#2110)
* feat(flights): split settings into 'only allow flights from current' and 'only show flights from current'

* fix(flights): only show actions button if pilot is allowd to fly the flight

---------

Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
2025-10-21 11:19:00 -05:00
Arthur Parienté
0695ed0823
chore(phpstan): setup and add level 1 (#2073)
* wip

* refactor: phpstan level 1

* refactor: how the amount of people per fare is passed in the simbrief resource

* ci: add phpstan

* refactor: phpstan level 2

* fix: new avatar column name

* refactor: rollback avatar column name

* fix: type in FareService
2025-08-20 11:32:48 -05:00
Arthur Parienté
a715ddf486
feat(admin): upgrade to Filament v4 (#2062)
* remove filament-clusters

* upgrade livewire

* remove coolsam modules and resolve modules providers before filament

* update dependencies

* run upgrade script

* refactor: module links plugin

* refactor: version widget

* refactor: admin panel

* refactor: create custom theme to use tailwindcss classes

* feat: add language switcher plugin

* refactor: migrate ActivityLogResource

* refactor: migrate SimBriefAirframes

* refactor: migrate AirlineResource

* refactor: translate FilesRelationManager

* refactor: migrate AirportResource

* refactor: translate FilesRelationManager

* refactor: translate ExpensesRelationManager

* refactor: migrate AwardResource

* refactor: migrate ExpenseResource

* refactor: migrate FareResource

* refactor: migrate FlightResource

* refactor: migrate InviteResource

* refactor: migrate ModuleResource

* refactor: migrate PageResource

* refactor: migrate PirepFieldResource

* refactor: migrate PirepResource

* refactor: some translations in the FlightResource

* refactor: migrate RankResource

* refactor: migrate SubfleetResource

* refactor: migrate AircraftResource

* refactor: migrate TyperatingResource

* refactor: migrate UserFieldResource

* refactor: migrate UserResource

* refactor: remove the emptyStateActions

* refactor: migrate Finances

* fix: SimBriefAirframe

* refactor: migrate Maintenance

* refactor: migrate Settings

* refactor: remove old pages views

* refactor: NavigationGroups

* feat: upgrade SystemPanel, Update page and add streamed response to migrations

* fix: canAccess in Updater

* feat: upgrade the whole SystemPanel

* remove tests migrations (oops)

* refactor: remove system theme

* chore: update filament

* fix: role system and custom permission generator

* fix: airport translation

* feat: translate dashboard widgets

* fix: version major number

* refactor: update IconColumn boolean

* chore: upgrade to vite v7 and fix some CVEs

* feat: run pint in parallel and add rector to pre-commit

* chore: downgrade openspout to support php 82

* feat(admin): add prefetching

* fix(pre-commit): run pint only on staged files

* fix(seeds): in_progress.yml seeds

* refactor: add full type coverage to admin

* fix: permissions in PirepFieldsAction and UserFieldsAction

* refactor: translation for PIREPs Fields

---------

Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
2025-08-13 10:20:23 -05:00
B.Fatih KOZ
357d25b29f
Fix Fuel (#2056) 2025-07-11 11:49:29 -05:00
Nabeel Shahzad
00c6942e73
Merge remote-tracking branch 'origin/feature/8.0'
# Conflicts:
#	.github/version.yml
#	app/Console/Commands/Version.php
#	app/Database/seeds/permissions.yml
#	app/Http/Controllers/Admin/UserController.php
#	app/Models/UserOAuthToken.php
#	app/Providers/AuthServiceProvider.php
#	composer.json
#	composer.lock
#	config/version.yml
#	modules/Sample/Providers/SampleServiceProvider.php
#	resources/views/admin/menu.blade.php
#	resources/views/layouts/seven/nav.blade.php
#	version.json
2025-05-05 11:14:06 -05:00
Arthur Parienté
28e2d33b88
[8.x] Add RectorPHP (#1986)
* Add rector

* Remove cache

* Run rector again

* Run pint

* Run Rector in CI

* Update composer.lock
2025-03-14 11:38:16 -05:00