Commit Graph

1895 Commits

Author SHA1 Message Date
Nabeel Shahzad
bb82e1fd8c docs(flights): document the with=bid token on get() 2026-07-06 16:45:11 -05:00
Nabeel Shahzad
37019e54ce fix(settings): reset the memo per queue job to avoid stale reads
The setting() memo is request-scoped under Octane via config/octane.php 'flush',
but a long-running queue worker is not flushed per job. Reset the memo in a
Queue::before hook so a worker observes settings changed by other processes;
otherwise, after an external store() busts the shared cache, the worker's next
Cache::remember() miss would re-read the stale memoized value and write it back,
poisoning the cache. Also corrects the clearMemo() docblock.
2026-07-06 16:18:45 -05:00
Nabeel Shahzad
e141d77aed fix(flights): batch bid resolution and eager-load subfleet aircraft
Review follow-ups on the with=bid fast-path:
- Resolve all returned flights' bids in one batched Bid::whereIn query instead
  of one query per flight in the search() loop (removes a reintroduced N+1).
- Eager-load bid.aircraft.subfleet.aircraft (SubfleetResource reads ->aircraft):
  matches the legacy accessible-fleet eager set, so serializing >=2 bid subfleets
  no longer lazy-loads (a LazyLoadingViolationException / 500 outside production).
- Store subfleets as an Eloquent Collection (not the Support Collection from
  pluck()) so the relation's type contract holds.
- Trim `with` tokens in search() to match hasBidToken().

Adds a multi-bid serialization test and a batched-query-count assertion.
2026-07-06 16:18:41 -05:00
Nabeel Shahzad
12cf279a1d perf(settings): memoize setting() reads per request (Octane-safe)
SettingService gains an in-instance memo so repeated setting() reads of the same
key resolve the source (Redis/DB) at most once per request. Bound as a singleton
and added to config/octane.php 'flush' so the memo lifetime is exactly one
request. store()/save() evict the key alongside forgetCache(); YamlDatabaseService
clears the memo after raw settings writes so same-request reads stay coherent.

Collapses the ~1,900-read setting() N+1 seen when serializing a large fleet.
2026-07-06 15:54:44 -05:00
Nabeel Shahzad
36b5ead2d5 feat(flights): with=bid fast-path + by-id search visibility
Add a controlled `bid` token to the flights get()/search() `with=` param.
When present, resolve the authenticated pilot's bid(s) on the flight, load
`bid.aircraft.subfleet.fares`, set `subfleets` to just those subfleet(s), and
skip accessibleSubfleetsFor entirely. No bid -> empty subfleets. `bid` is a
server-controlled whitelist token, never passed to Eloquent ->with().

search() also passes onlyActive = !filled('flight_id') so a keyed by-id lookup
returns the flight regardless of `visible` (parity with get()); browse search
still applies visible().

Cuts a bid briefing from expanding the whole accessible fleet (~234 subfleets on
the live VA) to ~5 indexed queries.
2026-07-06 15:54:08 -05:00
Arthur Parienté
4c9a67c2e5
[8.x] fix(finance): keep the journal relation fresh after initJournal() (#2255)
fix(finance): keep the journal relation fresh after initJournal()

The `!$this->journal` guard in JournalTrait::initJournal() lazy-loads the
journal relation and caches it as null. Saving the newly created journal
through the relation does not update that cache, so the creating model kept
returning null for ->journal until it was reloaded from the database.

Set the relation on the instance after saving, and add a regression test
asserting the journal is available immediately after creation.
2026-07-05 14:44:31 -05:00
Nabeel S.
ed1c45dba6
fix: change the plugin directory name (#2243)
* fix: change the plugin directory name to <author>-<name> (from registry_id)

* lower-case the dir name

* Fix test

* update the keyed name (x-y)

* cleanup the unused code

* More cleanup
2026-07-03 21:16:20 -05:00
Arthur Parienté
ca707bd069
[8.x] fix(addons): run enabled-module migrations and seeders reliably (#2251)
fix(addons): use standard Laravel database/ layout for module migrations and seeders

The installer's MigrationService and SeederService discovered addon
resources under a capitalized `Database/migrations` and `Database/seeds`
path, but the base module ServiceProvider (and standard Laravel) use
lowercase `database/migrations` and `database/seeders`. On a
case-sensitive filesystem the installer never found a module's lowercase
directories, so migrations were skipped in the installer flow and
seeders never ran.

Standardize discovery on the lowercase, standard-Laravel layout:
- SeederService scans `database/seeders`
- MigrationService scans `database/migrations`
- align the Sample scaffold directories and its provider
- update the addon migration/seeder feature-test fixtures

Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
2026-07-01 17:42:53 -05:00
Nabeel S.
6af8fc8585
Fix/admin links (#2250)
* fix: update docker configurations

* fix: older uuids were being gated
2026-07-01 13:39:09 -05:00
Arthur Parienté
7d4949beba
[8.x] feat(addons): add per-addon settings management (#2246)
* refactor(settings): extract typed setting casting into a shared trait

Move the bool/date/int/float/string casting match out of SettingService into a reusable CastsSettingValue trait so the upcoming addon settings service casts identically.

* feat(addons): add per-addon settings management

Addons declare settings via a HasSettings provider method; they are synced
into a dedicated addon_settings table on boot (preserving user values) and
read/written through addon_setting()/addon_setting_save() by alias or
registry_id. A shared AddonSettings Filament page is inherited by each addon
panel, scoped to that addon. Editing requires module access plus the new
edit:addon-settings permission. Sample module ships a reference schema.

* fix(addons): address review feedback on addon settings

- canAccess() excludes hidden settings so an addon with only hidden rows
  no longer shows an empty settings page
- dedupe normalized keys before upsert and always log orphans, even when an
  addon now declares no settings
- addon_setting() catches only SettingNotFound so unexpected errors surface
- log swallowed boot-time sync failures for observability
- add explicit types to sample_setting(); authenticate the page-access test
2026-07-01 10:31:59 -05:00
Rori Stumpf
d732e832aa
fix(pireps): clear stale ACARS track and logs when reusing a duplicate prefile (#2249)
* fix(pireps): clear stale ACARS track and logs when reusing a duplicate prefile

When a flight didn't close out (PIREP left IN_PROGRESS after a divert or
abort), restarting the same flight within the duplicate window made
prefile() adopt the old PIREP wholesale, keeping its FLIGHT_PATH rows
(a stray inbound line into the new departure) and LOG rows (a fused
flight log). Clear the reused leg's track and logs so the restarted
flight starts clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(pireps): assert planned ROUTE survives the duplicate-reuse cleanup

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:45:58 -05:00
Arthur Parienté
1bed33d94b
[8.x] fix(modules): register module database/migrations so artisan migrate runs them (#2245)
The base module ServiceProvider auto-wired config, routes, views,
translations, commands and listeners but never registered migrations, so
an enabled module's migrations were skipped by artisan migrate.

Load migrations from {root}/database/migrations, matching the module
app-layout convention. Add coverage proving the path is registered with
the migrator and that migrate creates the module table.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Addon database migrations are now automatically registered and loaded
from addon-specific migration directories, eliminating manual setup
requirements and streamlining addon development. This enables addon
creators to focus on functionality rather than infrastructure
configuration.

* **Tests**
* Feature tests were added to validate the automatic migration
registration system and verify migrations execute properly.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 17:56:04 -05:00
Nabeel S.
fbe81b9c90
[8.x] feat(permissions): replace filament-shield with an ability-based permission system (#2235)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added role & permission management UI with a matrix-based permission
picker.
* Introduced `permission:sync` to populate permissions from the registry
(with optional pruning).
* Added `permission:generate-policies` to create missing policy stubs on
demand.

* **Changes**
* Switched authorization to a permissions-based system with super-admin
bypass and consistent ability naming (including updated `edit:flight`
access).
* Updated Filament page and menu access gating to match the new
permission keys.
* Removed Filament Shield usage; streamlined policy authorization
behavior across resources and pages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-18 14:20:53 -05:00
Arthur Parienté
7609f6d1d9
fix(permissions): let view:modules grant access to module panels
canAccessPanel() short-circuited on module panels and never fell through to the legacy view:modules permission, so a user holding only view:modules was denied every module panel despite the documented fallback.
2026-06-18 20:34:35 +02:00
Arthur Parienté
b7491992de
Merge branch 'main' into feat/roles-permissions 2026-06-18 20:20:06 +02:00
Arthur Parienté
cfea5ce75e
refactor(support): extract lexical path helpers into App\Support\Filesystem
Move normalizePath()/isWithin() out of ManifestParser into a shared
App\Support\Filesystem so the same traversal-safe boundary check can be
reused. Apply it to the ZipSource zip-slip post-extraction guard, per
review feedback.
2026-06-18 20:16:53 +02:00
Arthur Parienté
6983091fdf
refactor(addons): rename registerFiles to loadAutoloadFiles
Clearer about which files the method loads (composer autoload.files),
per review feedback.
2026-06-18 20:16:45 +02:00
Arthur Parienté
d4427cf5a5
Merge remote-tracking branch 'origin/main' into fix/modules-files-autoload
# Conflicts:
#	app/Addons/Models/AddonBootCache.php
#	app/Addons/Services/AddonDiscoveryService.php
2026-06-18 20:13:02 +02:00
Nabeel S.
c7b4bc5a27
[8.x] fix(routing): register module routes last to allow core overrides (#2239)
Defers module route registration to the end of the routing stack. This
fixes the issue where core routes were matching first and blocking
module-level overrides.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Deferred loading of addon route files until the application completes
booting, improving startup performance and ensuring routes are
registered at the right lifecycle stage.
* Refreshed route name and action lookups after addon route registration
to keep route resolution consistent.
* **Tests**
* Updated addon service provider test setup to rely on the new deferred
boot behavior, removing now-unnecessary manual route lookup refresh
calls.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-18 13:05:09 -05:00
Arthur Parienté
cc351a15a7
fix(modules): point panel 'go back' link to frontend.dashboard.index 2026-06-17 23:06:45 +02:00
Arthur Parienté
72ce58af0d
fix(modules): address CodeRabbit review on module panels
- Configure ->login() on the module panel base so unauthenticated
  access has a defined redirect target.
- Use the named frontend.home route for the panel 'go back' item
  instead of a hardcoded '/'.
- Switch the panel switcher links to $panel->getUrl() (respecting
  panel routing/domain), falling back to the path-based URL.
- Use firstOrCreate for permissions/roles in the Sample panel test so
  it is deterministic when seeders already inserted them.
- Drop two inline comments flagged as non-complex.
2026-06-17 23:03:31 +02:00
Arthur Parienté
a0e3ca05ae
fix(addons): use lexical path checks to block manifest path traversal
realpath() returns false for non-existent targets, so an escaping
autoload.files or psr-4 entry (e.g. ../secret.php) whose file did not
yet exist skipped the boundary check and was cached. Normalise paths
lexically instead so traversal is rejected regardless of existence.
2026-06-17 22:49:19 +02:00
Arthur Parienté
13a2249b0c
feat(modules): give each module its own Filament panel with a switcher
Modules previously had their Filament resources injected into the main
admin panel and surfaced legacy addAdminLink() items as sidebar nav.
Invert this: each module owns a dedicated Filament panel and a navbar
panel switcher moves between the main panel and module panels.

- Add App\Contracts\Modules\PanelProvider: an abstract base that
  pre-configures id (= module key), path (admin/{key}), middleware,
  auth, branding, the shared admin theme/sidebar (viteTheme +
  sidebarWidth 14.5rem for visual consistency), a Dashboard page, the
  shared plugins, and discovery of the module's own
  Filament/{Resources,Pages,Widgets}. Modules register it via their
  providers list and override only moduleKey().
- Add PanelSwitcherPlugin: a topbar-left dropdown listing the main
  panel plus every module panel the user can access, on every panel.
- Stop injecting module components into the core admin/system panels:
  remove FilamentPanelExtender, its beforeResolving('filament') hook,
  probeFilament(), and the boot-cache filament field (schema bump).
- Remove the legacy link APIs (addAdminLink/addFrontendLink/
  registerLinks) and their blade/view consumers.
- Gate module panels via access:{module-key} in canAccessPanel(),
  with the view:modules fallback.
- Migrate the Sample module to ship its own panel provider and drop
  its old admin controller/route.
2026-06-17 22:41:34 +02:00
Arthur Parienté
c6f983ac6e
fix(addons): load composer autoload.files for enabled addons
The addon engine registered each enabled addon's PSR-4 namespace and
service providers but never loaded composer autoload.files, so module
global helpers (e.g. a helpers.php) were silently unavailable.

Thread autoload.files through ManifestParser -> AddonManifest ->
AddonBootCache -> AddonAutoLoader, requiring each file once per boot
cycle (after PSR-4, before providers). Bump the boot-cache schema so
pre-change caches rebuild. Add a Sample module helpers.php as the
reference pattern.
2026-06-17 21:31:27 +02:00
Arthur Parienté
c0f0e1564c
fix(routing): refresh route lookups after addon routes register
Addon routes load from a deferred booted() callback so they register
after core's routes. That also runs after the framework's own route
name/action lookup refresh, leaving addon routes unindexed and breaking
route() and Route::has() discovery for them.

Refresh the name/action lookups after loading, mirroring Laravel's
RouteServiceProvider. Drop the test's manual refreshNameLookups()
workaround so the existing Route::has() assertion covers the fix.
2026-06-17 21:18:56 +02:00
Arthur Parienté
87c1bc60bd
fix(routing): register module routes last to allow core overrides
Defers module route registration to the end of the routing stack. This
fixes the issue where core routes were matching first and blocking
module-level overrides.
2026-06-17 20:44:38 +02:00
Arthur Parienté
414e1c366b
refactor(fares): cast auto_price setting before negating toggle visibility
Make the three auto-price override columns cast the setting to bool before
negating, matching the price column above. Behavior is unchanged (boolean
settings already resolve to a real bool), but the intent is now explicit.
2026-06-16 22:45:40 +02:00
Arthur Parienté
5572b42fc9
feat(fares): expose auto-price fields in the admin UI
Add base_price/per_nm/multiplier to the fare form, the subfleet fares
relation manager (toggleable, shown when auto pricing is on and hidden
otherwise, with the static price column inverted), and the low_cost toggle
to the airline form. Round-trip the new columns through the fare
importers/exporters.
2026-06-16 22:17:40 +02:00
Arthur Parienté
1eef92c9a8
feat(fares): compute PIREP fare price from distance, category and airline
Add FareService::getAutoPrice() — (base + distance_nm * per_nm) * multiplier
* low_cost_factor, clamped to >= 0 — and apply it in saveToPirep() when
fares.auto_price is enabled. Distance is the great-circle nm between the
PIREP airports (new GeoService::airportDistance()), falling back to the
PIREP's stored distance, so it works for scheduled and free flights.
2026-06-16 22:17:23 +02:00
Arthur Parienté
91ed476cd8
feat(fares): add auto-pricing settings
Seed fares.auto_price (opt-in toggle, default off) and
fares.low_cost_multiplier, with an idempotent data migration for existing
installs. Render float-typed settings as numeric inputs on the Settings page.
2026-06-16 22:16:54 +02:00
Arthur Parienté
a0444e4fcb
feat(fares): add auto-price columns to fares, subfleets and airlines
Add base_price, per_nm and multiplier to fares (overridable per subfleet
via the subfleet_fare pivot) and a low_cost flag to airlines, with model
fillable/casts and factory defaults.
2026-06-16 22:14:05 +02:00
Arthur Pariente
4941cd5c9f
fix(permissions): address CodeRabbit review feedback
- Gate all RouteForge layers on edit:flight (route middleware, controller
  docblock, JS comment, test names) to match the page gate and can_commit
- Scope the permission:sync --prune query by guard_name so it cannot delete
  other guards' permissions
- Throw when the super-admin role is missing before legacy group import
  instead of dereferencing null
- Guard BasePolicy against an empty $subject to avoid malformed permission
  names
- Drop the redundant is_string() check on Panel::getPages() (always true)
2026-06-14 12:02:38 +02:00
Arthur Pariente
bdd11d07a7
test(permissions): cover policies, gate, registry and commands
- Add coverage for BasePolicy, the super-admin gate, PermissionRegistry,
  the roles matrix resource, page authorization, module panel access,
  and the sync/generate-policies commands
- Migrate existing tests off ShieldSeeder onto RolesPermissionsSeeder
- Invoke the commands by their real singular names (permission:sync,
  permission:generate-policies)
2026-06-14 11:20:19 +02:00
Arthur Pariente
47e088837f
feat(permissions): seed roles/permissions and drop ShieldSeeder
- Replace ShieldSeeder with RolesPermissionsSeeder, which creates the
  super-admin role and runs `permission:sync` to build the catalog
- Sync permissions during installer seeding and the legacy role import
- Resolve the super-admin role by Role::superAdminName() in the
  notifications subscriber and legacy group importer
2026-06-14 11:16:09 +02:00
Arthur Pariente
7721d9c502
feat(permissions): add permission sync and policy generation commands
- `permission:sync` persists the registry's permission catalog into the
  database (with an optional `--prune`) and flushes the spatie cache
- `permission:generate-policies` scaffolds thin BasePolicy subclasses for
  Filament resource models
2026-06-14 11:15:12 +02:00
Arthur Pariente
9197810d03
feat(filament): add roles permission matrix and authorize pages
- Add the Roles resource with a grouped permission matrix sourced from
  the PermissionRegistry (resource/page/module/custom permissions)
- Authorize pages and widgets via the AuthorizesAccess concern instead
  of filament-shield traits; drop HasWidgetShield and the
  FilamentShieldPlugin registration
- Gate RouteForge on `edit:flight` and hide the user roles field behind
  Role::superAdminName()
- Add the supporting translation strings
2026-06-14 11:15:05 +02:00
Arthur Pariente
192d098556
refactor(policies): rebuild Filament policies on BasePolicy
Reduce every Filament policy to a `$subject` slug and delegate all
methods to BasePolicy, which maps them onto view/edit/delete abilities.
Replaces the per-method filament-shield checks. Adds FlightBundlePolicy
and a Sample module policy to cover the new resources.
2026-06-14 11:14:26 +02:00
Arthur Pariente
8bb6914f6c
feat(permissions): add super-admin gate and model wiring
- Register a `Gate::before` hook so the super-admin role bypasses every
  permission check, replacing the removed filament-shield gate
- Add Role::superAdminName() as the single source for the bypass role
- Gate module panel access behind per-module `access:<module>`
  permissions via the PermissionRegistry, falling back to `view:modules`
- Bind PermissionRegistry as a singleton
- Point config/permission.php at the app's Role and Permission models
2026-06-14 11:14:15 +02:00
Arthur Pariente
d64c4ac577
feat(permissions): add ability-based permission core
Introduce the foundation that replaces filament-shield:

- Ability enum (view/edit/delete) mapping policy methods to permission
  names of the form `{ability}:{subject}`
- BasePolicy that resolves every Filament policy method onto an ability
  for a declared subject slug
- PermissionRegistry as the single source of truth for resource, page,
  module and custom permissions
- ProvidesPermissions contract plus the HasPermissionKey and
  AuthorizesAccess Filament concerns
- config/roles.php for the super-admin role name, guard and custom
  permission catalog
2026-06-14 11:13:40 +02:00
Nabeel Shahzad
61e543e5eb
fix tests (2) 2026-06-13 02:38:34 -05:00
Nabeel Shahzad
376456efb1
stupid pint errors 2026-06-13 00:40:11 -05:00
Nabeel Shahzad
2e9609b000
merge 2026-06-12 15:14:45 -05:00
Nabeel Shahzad
86aa5b8689
more refinement around the uninstall/removal process, added sample in for easier testing 2026-06-12 15:07:38 -05:00
Nabeel Shahzad
846308ecc9
feat(addons): refactor module links and improve addon handling
- Removed legacy sidebar rendering for module links in favor of native Filament sidebar items.
- Backfilled `addons.name` column using data from `module.json` or fallback derivation logic.
- Enhanced compatibility for Octane by ensuring idempotent handling of legacy admin links.
- Introduced `addon_vite` helper for rendering Vite tags from addon manifests.
- Updated `AddonAssetLinker` to ensure consistent handling of lower-cased paths for addon symlinks.
- Added extensive unit and feature tests for addon navigation, asset linking, and legacy handling.
- Updated configuration and boot cache logic to support test isolation and improved flexibility.
2026-06-12 15:04:38 -05:00
Nabeel Shahzad
39c08ee835
feat(flights): integrate ACARS addon check and update dependencies - minimum PHP version is n ow 8.4. A lot of fixes, verified the boot cache db upsert, etc 2026-06-12 15:04:38 -05:00
Nabeel Shahzad
3818559b96
feat(addons): implement addon installation, update, and validation workflows
- Introduced `AddonSource` interface and concrete `UrlSource` and `ZipSource` implementations for handling addon payloads.
- Added `AddonValidator` for extracting and validating addon manifests and autoload paths.
- Implemented `install` and `update` methods in `AddonRegistry` to support lifecycle management.
- Integrated Octane worker reloading functionality via `OctaneReloader`.
- Added unit and feature tests for installation, update handling, validation, and new behavior.
2026-06-12 15:04:37 -05:00
Nabeel Shahzad
f72bf460c4
feat(addons): introduce AddonAssetLinker and addons:relink command
- Added `AddonAssetLinker` to manage public asset symlinks for addons.
- Introduced the `addons:relink` Artisan command to rebuild asset links for enabled addons.
- Updated `AddonRegistry` to support asset linking/unlinking functionality.
- Adjusted configuration to set `addons.paths.assets` to `public/ext`.
- Added unit and feature tests for `AddonAssetLinker` and the new command.
2026-06-12 15:04:37 -05:00
Nabeel Shahzad
3ebb6beef7
refactor(helpers): remove unused NWIDART Module facade import 2026-06-12 15:04:37 -05:00
Nabeel Shahzad
d0cf54c3de
refactor(addons): remove Module shim, legacy NWIDART bindings, and update controllers/tests
- Removed `Module` and `ModuleRepository` compatibility shims.
- Replaced NWIDART `Module` facade usages with the new `AddonRegistry`.
- Updated frontend controllers to use `AddonRegistry` for module handling.
- Added new feature tests to cover `AddonRegistry` lifecycle and rebuild cases.
- Updated bootstrapping logic and service bindings to drop NWIDART dependencies.

# Conflicts:
#	app/Models/Flight.php
#	app/Support/Utils.php
2026-06-12 15:04:35 -05:00
Nabeel Shahzad
282a485fef
feat(addons): add AddonNotFoundException 2026-06-12 14:58:44 -05:00