Search flights by subfleet #484 (#506)

* API level search of flights #484

* Add Subfleet to flights page for search
This commit is contained in:
Nabeel S 2020-01-16 17:36:03 -05:00 committed by GitHub
parent d03a77bd4b
commit 2415caab54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 185 additions and 94 deletions

View File

@ -93,6 +93,7 @@ class FlightController extends Controller
return response($e, 503);
}
// TODO: Remove any flights here that a user doesn't have permissions to
foreach ($flights as $flight) {
$this->flightSvc->filterSubfleets(Auth::user(), $flight);
}

View File

@ -8,50 +8,68 @@ use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
use App\Repositories\Criteria\WhereCriteria;
use App\Repositories\FlightRepository;
use App\Repositories\SubfleetRepository;
use App\Services\GeoService;
use Flash;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Log;
use Illuminate\Support\Facades\Log;
use Prettus\Repository\Criteria\RequestCriteria;
use Prettus\Repository\Exceptions\RepositoryException;
/**
* Class FlightController
*/
class FlightController extends Controller
{
private $airlineRepo;
private $airportRepo;
private $flightRepo;
private $subfleetRepo;
private $geoSvc;
/**
* FlightController constructor.
*
* @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo
* @param FlightRepository $flightRepo
* @param GeoService $geoSvc
* @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo
* @param FlightRepository $flightRepo
* @param GeoService $geoSvc
* @param SubfleetRepository $subfleetRepo
*/
public function __construct(
AirlineRepository $airlineRepo,
AirportRepository $airportRepo,
FlightRepository $flightRepo,
GeoService $geoSvc
GeoService $geoSvc,
SubfleetRepository $subfleetRepo
) {
$this->airlineRepo = $airlineRepo;
$this->airportRepo = $airportRepo;
$this->flightRepo = $flightRepo;
$this->geoSvc = $geoSvc;
$this->subfleetRepo = $subfleetRepo;
}
/**
* @param Request $request
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request)
{
return $this->search($request);
}
/**
* Make a search request using the Repository search
*
* @param Request $request
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*
* @return mixed
*/
public function search(Request $request)
{
$where = [
'active' => true,
@ -67,14 +85,17 @@ class FlightController extends Controller
$where['dpt_airport_id'] = Auth::user()->curr_airport_id;
}
$this->flightRepo->resetCriteria();
try {
$this->flightRepo->searchCriteria($request);
$this->flightRepo->pushCriteria(new WhereCriteria($request, $where));
$this->flightRepo->pushCriteria(new RequestCriteria($request));
} catch (RepositoryException $e) {
Log::emergency($e);
}
$flights = $this->flightRepo
$flights = $this->flightRepo->searchCriteria($request)
->with(['dpt_airport', 'arr_airport', 'airline'])
->orderBy('flight_number', 'asc')
->orderBy('route_leg', 'asc')
@ -84,10 +105,15 @@ class FlightController extends Controller
->pluck('flight_id')->toArray();
return view('flights.index', [
'airlines' => $this->airlineRepo->selectBoxList(true),
'airports' => $this->airportRepo->selectBoxList(true),
'flights' => $flights,
'saved' => $saved_flights,
'airlines' => $this->airlineRepo->selectBoxList(true),
'airports' => $this->airportRepo->selectBoxList(true),
'flights' => $flights,
'saved' => $saved_flights,
'subfleets' => $this->subfleetRepo->selectBoxList(true),
'flight_number' => $request->input('flight_number'),
'arr_icao' => $request->input('arr_icao'),
'dep_icao' => $request->input('dep_icao'),
'subfleet_id' => $request->input('subfleet_id'),
]);
}
@ -114,62 +140,12 @@ class FlightController extends Controller
]);
}
/**
* Make a search request using the Repository search
*
* @param Request $request
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function search(Request $request)
{
$where = [
'active' => true,
'visible' => true,
];
if (setting('pilots.restrict_to_company')) {
$where['airline_id'] = Auth::user()->airline_id;
}
// default restrictions on the flights shown. Handle search differently
if (setting('pilots.only_flights_from_current')) {
$where['dpt_airport_id'] = Auth::user()->curr_airport_id;
}
$this->flightRepo->resetCriteria();
try {
$this->flightRepo->pushCriteria(new WhereCriteria($request, $where));
} catch (RepositoryException $e) {
Log::emergency($e);
}
$flights = $this->flightRepo->searchCriteria($request)
->with(['dpt_airport', 'arr_airport', 'airline'])
->orderBy('flight_number', 'asc')
->orderBy('route_leg', 'asc')
->paginate();
$saved_flights = Bid::where('user_id', Auth::id())
->pluck('flight_id')->toArray();
return view('flights.index', [
'airlines' => $this->airlineRepo->selectBoxList(true),
'airports' => $this->airportRepo->selectBoxList(true),
'flights' => $flights,
'saved' => $saved_flights,
]);
}
/**
* Show the flight information page
*
* @param $id
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
* @return mixed
*/
public function show($id)
{

View File

@ -4,6 +4,7 @@ namespace App\Repositories\Criteria;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
@ -17,17 +18,20 @@ class WhereCriteria implements CriteriaInterface
*/
protected $request;
protected $where;
protected $relations;
/**
* Create a new Where search.
*
* @param $request
* @param $where
* @param Request $request
* @param array $where
* @param array [$relations] Any whereHas (key = table name, value = array of criterea
*/
public function __construct($request, $where)
public function __construct(Request $request, $where, $relations = [])
{
$this->request = $request;
$this->where = $where;
$this->relations = $relations;
}
/**
@ -46,6 +50,17 @@ class WhereCriteria implements CriteriaInterface
$model = $model->where($this->where);
}
// See if any relationships need to be included in this WHERE
if ($this->relations) {
foreach ($this->relations as $relation => $criterea) {
$model = $model
->with($relation)
->whereHas($relation, function (Builder $query) use ($criterea) {
$query->where($criterea);
});
}
}
return $model;
}
}

View File

@ -74,6 +74,7 @@ class FlightRepository extends Repository implements CacheableInterface
public function searchCriteria(Request $request, bool $only_active = true): self
{
$where = [];
$relations = [];
if ($only_active === true) {
$where['active'] = $only_active;
@ -81,48 +82,55 @@ class FlightRepository extends Repository implements CacheableInterface
}
if ($request->filled('flight_id')) {
$where['id'] = $request->flight_id;
$where['id'] = $request->input('flight_id');
}
if ($request->filled('airline_id')) {
$where['airline_id'] = $request->airline_id;
$where['airline_id'] = $request->input('airline_id');
}
if ($request->filled('flight_number')) {
$where['flight_number'] = $request->flight_number;
$where['flight_number'] = $request->input('flight_number');
}
if ($request->filled('route_code')) {
$where['route_code'] = $request->route_code;
$where['route_code'] = $request->input('route_code');
}
if ($request->filled('dpt_airport_id')) {
$where['dpt_airport_id'] = strtoupper($request->dpt_airport_id);
$where['dpt_airport_id'] = strtoupper($request->input('dpt_airport_id'));
}
if ($request->filled('dep_icao')) {
$where['dpt_airport_id'] = strtoupper($request->dep_icao);
$where['dpt_airport_id'] = strtoupper($request->input('dep_icao'));
}
if ($request->filled('arr_airport_id')) {
$where['arr_airport_id'] = strtoupper($request->arr_airport_id);
$where['arr_airport_id'] = strtoupper($request->input('arr_airport_id'));
}
if ($request->filled('arr_icao')) {
$where['arr_airport_id'] = strtoupper($request->arr_icao);
$where['arr_airport_id'] = strtoupper($request->input('arr_icao'));
}
// Distance, greater than
if ($request->filled('dgt')) {
$where[] = ['distance', '>=', $request->dgt];
$where[] = ['distance', '>=', $request->input('dgt')];
}
// Distance, less than
if ($request->filled('dlt')) {
$where[] = ['distance', '<=', $request->dlt];
$where[] = ['distance', '<=', $request->input('dlt')];
}
$this->pushCriteria(new WhereCriteria($request, $where));
// Do a special query for finding the child subfleets
if ($request->filled('subfleet_id')) {
$relations['subfleets'] = [
'subfleets.id' => $request->input('subfleet_id'),
];
}
$this->pushCriteria(new WhereCriteria($request, $where, $relations));
return $this;
}

View File

@ -7,9 +7,6 @@ use App\Models\Subfleet;
use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Traits\CacheableRepository;
/**
* Class SubfleetRepository
*/
class SubfleetRepository extends Repository implements CacheableInterface
{
use CacheableRepository;
@ -26,4 +23,27 @@ class SubfleetRepository extends Repository implements CacheableInterface
{
return Subfleet::class;
}
/**
* Return the list of aircraft formatted for a select box
*
* @param bool $add_blank
*
* @return array
*/
public function selectBoxList($add_blank = false): array
{
$retval = [];
$items = $this->all();
if ($add_blank) {
$retval[''] = '';
}
foreach ($items as $i) {
$retval[$i->id] = $i->name;
}
return $retval;
}
}

View File

@ -24,6 +24,7 @@ return [
'arrival' => 'Arrival',
'aircraft' => 'Aircraft',
'airline' => 'Airline',
'subfleet' => 'Subfleet',
'distance' => 'Distance',
'fuel' => 'Fuel',
'metar' => 'METAR',

View File

@ -24,6 +24,7 @@ return [
'arrival' => 'Llegada',
'aircraft' => 'Aeronave',
'airline' => 'Aerolínea',
'subfleet' => 'Subfleet',
'distance' => 'Distancía',
'fuel' => 'Combustible',
'metar' => 'METAR',

View File

@ -24,6 +24,7 @@ return [
'arrival' => 'Arrivo',
'aircraft' => 'Aereomobile',
'airline' => 'Compagnia Aerea',
'subfleet' => 'Subfleet',
'distance' => 'Distanza',
'fuel' => 'Carburante',
'metar' => 'METAR',

View File

@ -22,6 +22,11 @@
{{ Form::select('arr_icao', $airports, null , ['class' => 'form-control select2']) }}
</div>
<div style="margin-top: 10px; margin-left: 5px;">
<p>@lang('common.subfleet')</p>
{{ Form::select('subfleet_id', $subfleets, null , ['class' => 'form-control select2']) }}
</div>
<div class="clear" style="margin-top: 10px; margin-left: 5px;">
{{ Form::submit(__('common.find'), ['class' => 'btn btn-primary']) }}&nbsp;
<a href="{{ route('frontend.flights.index') }}">@lang('common.reset')</a>

View File

@ -23,6 +23,11 @@ paths:
type: string
required: false
description: ID of the airline
- name: subfleet_id
in: query
type: string
required: false
description: 'The subfleet to search on. Flights would be returned, but if the subfleets are empty, that means the pilot doesn\'t have permissions for that flight'
- name: flight_number
in: query
type: string
@ -48,11 +53,11 @@ paths:
type: integer
required: false
description: Flights with a distance greater than
- name: dgt
- name: dlt
in: query
type: integer
required: false
description: Flights with a distance greater than
description: Flights with a distance less than
produces:
- application/json
responses:

View File

@ -1,6 +1,9 @@
<?php
use App\Cron\Nightly\SetActiveFlights;
use App\Events\CronNightly;
use App\Models\Enums\Days;
use App\Models\Enums\NavaidType;
use App\Models\Flight;
use App\Models\User;
use App\Repositories\SettingRepository;
@ -21,6 +24,13 @@ class FlightTest extends TestCase
$this->settingsRepo = app(SettingRepository::class);
}
/**
* Add a single flight
*
* @param $user
*
* @return mixed
*/
public function addFlight($user)
{
$flight = factory(App\Models\Flight::class)->create([
@ -36,6 +46,25 @@ class FlightTest extends TestCase
return $flight;
}
/**
* Add a given number of flights for a subfleet
*
* @param $subfleet
* @param $num_flights
*
* @return \App\Models\Flight[]
*/
public function addFlightsForSubfleet($subfleet, $num_flights)
{
return factory(App\Models\Flight::class, $num_flights)->create([
'airline_id' => $subfleet->airline->id,
])->each(function (Flight $f) use ($subfleet) {
$f->subfleets()->syncWithoutDetaching([
$subfleet->id,
]);
});
}
/**
* Test adding a flight and also if there are duplicates
*/
@ -148,10 +177,7 @@ class FlightTest extends TestCase
$this->assertEquals($first_point['id'], $route[0]->id);
$this->assertEquals($first_point['name'], $route[0]->name);
$this->assertEquals($first_point['type']['type'], $route[0]->type);
$this->assertEquals(
$first_point['type']['name'],
\App\Models\Enums\NavaidType::label($route[0]->type)
);
$this->assertEquals($first_point['type']['name'], NavaidType::label($route[0]->type));
}
/**
@ -173,6 +199,38 @@ class FlightTest extends TestCase
$res->assertJsonCount(5, 'data');
}
/**
* Search for flights based on a subfleet. If subfleet is blank
*/
public function testSearchFlightBySubfleet()
{
$airline = factory(App\Models\Airline::class)->create();
$subfleetA = factory(App\Models\Subfleet::class)->create(['airline_id' => $airline->id]);
$subfleetB = factory(App\Models\Subfleet::class)->create(['airline_id' => $airline->id]);
$rank = $this->createRank(0, [$subfleetB->id]);
$this->user = factory(App\Models\User::class)->create([
'airline_id' => $airline->id,
'rank_id' => $rank->id,
]);
$this->addFlightsForSubfleet($subfleetA, 5);
$this->addFlightsForSubfleet($subfleetB, 10);
// search specifically for a given subfleet
//$query = 'subfleet_id='.$subfleetB->id;
$query = 'subfleet_id='.$subfleetB->id;
$res = $this->get('/api/flights/search?'.$query);
$res->assertStatus(200);
$res->assertJsonCount(10, 'data');
$body = $res->json('data');
collect($body)->each(function ($flight) use ($subfleetB) {
self::assertNotEmpty($flight['subfleets']);
self::assertEquals($subfleetB->id, $flight['subfleets'][0]['id']);
});
}
/**
* Test the bitmasks that they work for setting the day of week and
* then retrieving by searching on those
@ -227,8 +285,8 @@ class FlightTest extends TestCase
]);
// Run the event that will enable/disable flights
$event = new \App\Events\CronNightly();
(new \App\Cron\Nightly\SetActiveFlights())->handle($event);
$event = new CronNightly();
(new SetActiveFlights())->handle($event);
$res = $this->get('/api/flights');
$body = $res->json('data');
@ -274,8 +332,8 @@ class FlightTest extends TestCase
]);
// Run the event that will enable/disable flights
$event = new \App\Events\CronNightly();
(new \App\Cron\Nightly\SetActiveFlights())->handle($event);
$event = new CronNightly();
(new SetActiveFlights())->handle($event);
$res = $this->get('/api/flights');
$body = $res->json('data');
@ -313,8 +371,8 @@ class FlightTest extends TestCase
]);
// Run the event that will enable/disable flights
$event = new \App\Events\CronNightly();
(new \App\Cron\Nightly\SetActiveFlights())->handle($event);
$event = new CronNightly();
(new SetActiveFlights())->handle($event);
$res = $this->get('/api/flights');
$body = $res->json('data');