* Add flight level field to PIREP field closes #401

* Default value for distance 0 closes #400

* Block airline deletion if assets exist #367

* Formatting

* Move some of the base exception classes

* Fix skin references to use settings table

* Set default for theme name if setting is wrong
This commit is contained in:
Nabeel S 2019-11-19 10:06:07 -05:00 committed by GitHub
parent c643833496
commit 3ec64c989b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 237 additions and 117 deletions

View File

@ -3,8 +3,12 @@
namespace App\Exceptions;
use Symfony\Component\HttpKernel\Exception\HttpException as SymfonyHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
abstract class HttpException extends SymfonyHttpException
/**
* Abstract class for an exception which needs to satisty the RFC 78707 interface
*/
abstract class AbstractHttpException extends SymfonyHttpException implements HttpExceptionInterface
{
/**
* Return the RFC 7807 error type (without the URL root)
@ -45,7 +49,7 @@ abstract class HttpException extends SymfonyHttpException
/**
* Return a response object that can be used by Laravel
*
* @return \Illuminate\Http\JsonResponse
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
*/
public function getResponse()
{
@ -53,11 +57,6 @@ abstract class HttpException extends SymfonyHttpException
$headers['content-type'] = 'application/problem+json';
$headers = array_merge($headers, $this->getHeaders());
return response()
->json(
$this->getJson(),
$this->getStatusCode(),
$headers
);
return response()->json($this->getJson(), $this->getStatusCode(), $headers);
}
}

View File

@ -7,7 +7,7 @@ use App\Models\Aircraft;
/**
* Class AircraftNotAtAirport
*/
class AircraftNotAtAirport extends HttpException
class AircraftNotAtAirport extends AbstractHttpException
{
public const MESSAGE = 'The aircraft is not at the departure airport';

View File

@ -5,7 +5,7 @@ namespace App\Exceptions;
use App\Models\Aircraft;
use App\Models\User;
class AircraftPermissionDenied extends HttpException
class AircraftPermissionDenied extends AbstractHttpException
{
public const MESSAGE = 'User is not allowed to fly this aircraft';

View File

@ -2,7 +2,7 @@
namespace App\Exceptions;
class AirportNotFound extends HttpException
class AirportNotFound extends AbstractHttpException
{
private $icao;

View File

@ -1,11 +1,10 @@
<?php
namespace App\Exceptions\Converters;
namespace App\Exceptions;
use App\Exceptions\HttpException;
use Exception;
class NotFound extends HttpException
class AssetNotFound extends AbstractHttpException
{
private $exception;

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\Flight;
class BidExistsForFlight extends HttpException
class BidExistsForFlight extends AbstractHttpException
{
private $flight;

View File

@ -2,10 +2,10 @@
namespace App\Exceptions\Converters;
use App\Exceptions\HttpException;
use App\Exceptions\AbstractHttpException;
use Exception;
class GenericException extends HttpException
class GenericExceptionAbstract extends AbstractHttpException
{
private $exception;

View File

@ -2,10 +2,10 @@
namespace App\Exceptions\Converters;
use App\Exceptions\HttpException;
use App\Exceptions\AbstractHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException as SymfonyHttpException;
class SymfonyException extends HttpException
class SymfonyException extends AbstractHttpException
{
private $exception;

View File

@ -2,11 +2,10 @@
namespace App\Exceptions\Converters;
use App\Exceptions\HttpException;
use Illuminate\Support\Facades\Log;
use App\Exceptions\AbstractHttpException;
use Illuminate\Validation\ValidationException as IlluminateValidationException;
class ValidationException extends HttpException
class ValidationException extends AbstractHttpException
{
private $validationException;
private $errorDetail;

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\Flight;
class DuplicateFlight extends HttpException
class DuplicateFlight extends AbstractHttpException
{
private $flight;

View File

@ -2,8 +2,7 @@
namespace App\Exceptions;
use App\Exceptions\Converters\GenericException;
use App\Exceptions\Converters\NotFound;
use App\Exceptions\Converters\GenericExceptionAbstract;
use App\Exceptions\Converters\SymfonyException;
use App\Exceptions\Converters\ValidationException;
use Exception;
@ -33,7 +32,7 @@ class Handler extends ExceptionHandler
protected $dontReport = [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
AbstractHttpException::class,
IlluminateValidationException::class,
ModelNotFoundException::class,
SymfonyHttpException::class,
@ -53,7 +52,7 @@ class Handler extends ExceptionHandler
if ($request->is('api/*')) {
Log::error('API Error', $exception->getTrace());
if ($exception instanceof HttpException) {
if ($exception instanceof AbstractHttpException) {
return $exception->getResponse();
}
@ -63,7 +62,7 @@ class Handler extends ExceptionHandler
if ($exception instanceof ModelNotFoundException ||
$exception instanceof NotFoundHttpException) {
$error = new NotFound($exception);
$error = new AssetNotFound($exception);
return $error->getResponse();
}
@ -79,11 +78,11 @@ class Handler extends ExceptionHandler
return $error->getResponse();
}
$error = new GenericException($exception);
$error = new GenericExceptionAbstract($exception);
return $error->getResponse();
}
if ($exception instanceof HttpException
if ($exception instanceof AbstractHttpException
&& $exception->getStatusCode() === 403) {
return redirect()->guest('login');
}
@ -112,24 +111,25 @@ class Handler extends ExceptionHandler
/**
* Render the given HttpException.
*
* @param HttpException $e
* @param AbstractHttpException $e
*
* @return \Illuminate\Http\Response|Response
*/
protected function renderHttpException(HttpExceptionInterface $e)
{
Flash::error($e->getMessage());
$status = $e->getStatusCode();
view()->replaceNamespace('errors', [
resource_path('views/layouts/'.config('phpvms.skin').'/errors'),
resource_path('views/layouts/'.setting('general.theme', 'default').'/errors'),
resource_path('views/errors'),
__DIR__.'/views',
]);
if (view()->exists("errors::{$status}")) {
//if (view()->exists('layouts' . config('phpvms.skin') .'.errors.' .$status)) {
return response()->view("errors::{$status}", [
'exception' => $e,
'SKIN_NAME' => config('phpvms.skin'),
'SKIN_NAME' => setting('general.theme', 'default'),
], $status, $e->getHeaders());
}

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\Pirep;
class PirepCancelNotAllowed extends HttpException
class PirepCancelNotAllowed extends AbstractHttpException
{
private $pirep;

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\Pirep;
class PirepCancelled extends HttpException
class PirepCancelled extends AbstractHttpException
{
private $pirep;

View File

@ -2,7 +2,7 @@
namespace App\Exceptions;
class Unauthenticated extends HttpException
class Unauthenticated extends AbstractHttpException
{
public function __construct()
{

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\User;
class UserBidLimit extends HttpException
class UserBidLimit extends AbstractHttpException
{
private $user;

View File

@ -5,7 +5,7 @@ namespace App\Exceptions;
use App\Models\Airport;
use App\Models\User;
class UserNotAtAirport extends HttpException
class UserNotAtAirport extends AbstractHttpException
{
public const MESSAGE = 'Pilot is not at the departure airport';

View File

@ -4,7 +4,7 @@ namespace App\Exceptions;
use App\Models\User;
class UserPilotIdExists extends HttpException
class UserPilotIdExists extends AbstractHttpException
{
public const MESSAGE = 'A user with this pilot ID already exists';

View File

@ -6,27 +6,28 @@ use App\Contracts\Controller;
use App\Http\Requests\CreateAirlineRequest;
use App\Http\Requests\UpdateAirlineRequest;
use App\Repositories\AirlineRepository;
use App\Services\AirlineService;
use App\Support\Countries;
use Flash;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laracasts\Flash\Flash;
use Prettus\Repository\Criteria\RequestCriteria;
use Response;
/**
* Class AirlinesController
*/
class AirlinesController extends Controller
{
private $airlineRepo;
private $airlineSvc;
/**
* AirlinesController constructor.
*
* @param AirlineRepository $airlinesRepo
* @param $airlineSvc
*/
public function __construct(AirlineRepository $airlinesRepo)
public function __construct(AirlineRepository $airlinesRepo, AirlineService $airlineSvc)
{
$this->airlineRepo = $airlinesRepo;
$this->airlineSvc = $airlineSvc;
}
/**
@ -145,13 +146,18 @@ class AirlinesController extends Controller
*/
public function destroy($id)
{
$airlines = $this->airlineRepo->findWithoutFail($id);
$airline = $this->airlineRepo->findWithoutFail($id);
if (empty($airlines)) {
if (empty($airline)) {
Flash::error('Airlines not found');
return redirect(route('admin.airlines.index'));
}
if (!$this->airlineSvc->canDeleteAirline($airline)) {
Flash::error('Airlines cannot be deleted; flights/PIREPs/subfleets exist');
return redirect(route('admin.airlines.index'));
}
$this->airlineRepo->delete($id);
Flash::success('Airlines deleted successfully.');

View File

@ -22,15 +22,13 @@ class Acars extends Response
$res = parent::toArray($request);
// Set these to the response units
if (!empty($res['distance'])) {
$distance = new Distance($res['distance'], config('phpvms.internal_units.distance'));
$res['distance'] = $distance->getResponseUnits();
}
$distance = !empty($res['distance']) ? $res['distance'] : 0;
$distance = new Distance($distance, config('phpvms.internal_units.distance'));
$res['distance'] = $distance->getResponseUnits();
if (!empty($res['fuel'])) {
$fuel = new Fuel($res['fuel'], config('phpvms.internal_units.fuel'));
$res['fuel'] = $fuel->getResponseUnits();
}
$fuel = !empty($res['fuel']) ? $res['fuel'] : 0;
$fuel = new Fuel($fuel, config('phpvms.internal_units.fuel'));
$res['fuel'] = $fuel->getResponseUnits();
return $res;
}

View File

@ -96,4 +96,14 @@ class Airline extends Model
{
$this->attributes['icao'] = strtoupper($icao);
}
public function flights()
{
return $this->belongsTo(Flight::class, 'airline_id');
}
public function pireps()
{
return $this->belongsTo(Pirep::class, 'airline_id');
}
}

View File

@ -35,6 +35,7 @@ use Illuminate\Support\Collection;
* @property float fuel_used
* @property float distance
* @property float planned_distance
* @property int level
* @property string route
* @property int score
* @property User user
@ -127,6 +128,7 @@ class Pirep extends Model
'arr_airport_id' => 'required',
'block_fuel' => 'required|numeric',
'fuel_used' => 'required|numeric',
'level' => 'nullable|numeric',
'notes' => 'nullable',
'route' => 'nullable',
];
@ -296,36 +298,6 @@ class Pirep extends Model
return $field_values->sortBy('source');
}
/**
* Look up the flight, based on the PIREP flight info
*
* @param mixed $value
*
* @return Flight|null
*/
/*public function getFlightAttribute(): ?Flight
{
if (!empty($this->flight_id)) {
return Flight::find($this->flight_id);
}
$where = [
'airline_id' => $this->airline_id,
'flight_number' => $this->flight_number,
'active' => true,
];
if (filled($this->route_code)) {
$where['route_code'] = $this->route_code;
}
if (filled($this->route_leg)) {
$where['route_leg'] = $this->route_leg;
}
return Flight::where($where)->first();
}*/
/**
* Set the amount of fuel used
*

View File

@ -18,6 +18,7 @@ use App\Models\Traits\FilesTrait;
* @property float cost_block_hour
* @property float cost_delay_minute
* @property Airline airline
* @property int airline_id
*/
class Subfleet extends Model
{

View File

@ -0,0 +1,56 @@
<?php
namespace App\Services;
use App\Contracts\Service;
use App\Models\Airline;
use App\Repositories\AirlineRepository;
use App\Repositories\FlightRepository;
use App\Repositories\PirepRepository;
use App\Repositories\SubfleetRepository;
class AirlineService extends Service
{
private $airlineRepo;
private $flightRepo;
private $pirepRepo;
private $subfleetRepo;
public function __construct(
AirlineRepository $airlineRepo,
FlightRepository $flightRepo,
PirepRepository $pirepRepo,
SubfleetRepository $subfleetRepo
) {
$this->airlineRepo = $airlineRepo;
$this->flightRepo = $flightRepo;
$this->pirepRepo = $pirepRepo;
$this->subfleetRepo = $subfleetRepo;
}
/**
* Can the airline be deleted? Check if there are flights, etc associated with it
*
* @param Airline $airline
*
* @return bool
*/
public function canDeleteAirline(Airline $airline): bool
{
// Check these asset counts in these repositories
$repos = [
$this->pirepRepo,
$this->flightRepo,
$this->subfleetRepo,
];
$w = ['airline_id' => $airline->id];
foreach ($repos as $repo) {
if ($repo->count($w) > 0) {
return false;
}
}
return true;
}
}

View File

@ -123,8 +123,7 @@ if (!function_exists('skin_view')) {
return view($template, $vars, $merge_data);
}
// TODO: Look for an overridden template in a special folder
$tpl = 'layouts/'.config('phpvms.skin').'/'.$template;
$tpl = 'layouts/'.setting('general.theme', 'default').'/'.$template;
return view($tpl, $vars, $merge_data);
}
@ -134,6 +133,14 @@ if (!function_exists('skin_view')) {
* Shortcut for retrieving a setting value
*/
if (!function_exists('setting')) {
/**
* Read a setting from the settings table
*
* @param $key
* @param mixed $default
*
* @return mixed|null
*/
function setting($key, $default = null)
{
$settingRepo = app('setting');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,14 @@
{
"/assets/frontend/js/app.js": "/assets/frontend/js/app.js?id=88270e919809fead277c",
"/assets/frontend/js/app.js": "/assets/frontend/js/app.js?id=218bdf369c2dd02856cc",
"/assets/frontend/css/now-ui-kit.css": "/assets/frontend/css/now-ui-kit.css?id=20b82d8dbacf7e058df2",
"/assets/admin/css/vendor.min.css": "/assets/admin/css/vendor.min.css?id=9f24c5e6612e74065901",
"/assets/frontend/js/app.js.map": "/assets/frontend/js/app.js.map?id=0019c21255cb4eca716d",
"/assets/frontend/js/app.js.map": "/assets/frontend/js/app.js.map?id=6b1ab9e89d979ba41fc4",
"/assets/frontend/css/now-ui-kit.css.map": "/assets/frontend/css/now-ui-kit.css.map?id=fdc4f42ad9047d073145",
"/assets/admin/css/vendor.min.css.map": "/assets/admin/css/vendor.min.css.map?id=c266c31652dea865307c",
"/assets/admin/js/app.js": "/assets/admin/js/app.js?id=cd8b03d3b7bd54e960ca",
"/assets/admin/js/app.js.map": "/assets/admin/js/app.js.map?id=a43168e6da458119da61",
"/assets/installer/js/app.js": "/assets/installer/js/app.js?id=7eb8b66dce0064082abc",
"/assets/installer/js/app.js.map": "/assets/installer/js/app.js.map?id=b277e94eb1368204a7d5",
"/assets/admin/js/app.js": "/assets/admin/js/app.js?id=37a4ecf79cdc64e724e5",
"/assets/admin/js/app.js.map": "/assets/admin/js/app.js.map?id=295a34b9c67c5a178d8a",
"/assets/installer/js/app.js": "/assets/installer/js/app.js?id=aa8661200da32787441c",
"/assets/installer/js/app.js.map": "/assets/installer/js/app.js.map?id=917f0f8bcd02d558f615",
"/assets/fonts/glyphicons-halflings-regular.woff2": "/assets/fonts/glyphicons-halflings-regular.woff2?id=349344e92fb16221dd56",
"/assets/admin/fonts/glyphicons-halflings-regular.woff2": "/assets/admin/fonts/glyphicons-halflings-regular.woff2?id=349344e92fb16221dd56",
"/assets/admin/img/clear.png": "/assets/admin/img/clear.png?id=63b3af84650a0145d61a",

View File

@ -3,8 +3,10 @@ const rivets = require('rivets');
/**
* Generic formatter to prepend
*
* @param value
* @param prepend
*
* @returns {*}
*/
rivets.formatters.prepend = function (value, prepend) {
@ -13,7 +15,9 @@ rivets.formatters.prepend = function (value, prepend) {
/**
* Format minutes into HHh MMm
*
* @param value
*
* @returns {string}
*/
rivets.formatters.time_hm = function (value) {
@ -26,6 +30,7 @@ rivets.formatters.time_hm = function (value) {
*
* @param value
* @param len
*
* @returns {boolean}
*/
rivets.formatters.gt = (value, len) => value.length > len;
@ -34,6 +39,7 @@ rivets.formatters.gt = (value, len) => value.length > len;
*
* @param value
* @param len
*
* @returns {boolean}
*/
rivets.formatters.lt = (value, len) => value.length < len;
@ -42,6 +48,15 @@ rivets.formatters.lt = (value, len) => value.length < len;
*
* @param value
* @param len
*
* @returns {boolean}
*/
rivets.formatters.eq = (value, len) => value.length > len;
/**
* Use a default value if value is null or blank
*
* @param value Value to use
* @param def Default value to use if value is null
*/
rivets.formatters.fallback = (value, def) => value || def;

View File

@ -95,7 +95,7 @@ flight reports that have been filed. You've been warned!
</div>
<div class="row">
<div class="col-3">
<div class="col-6">
{{ Form::label('hours', __('flights.flighttime')) }}
@if(!empty($pirep) && $pirep->read_only)
<p>
@ -124,6 +124,22 @@ flight reports that have been filed. You've been warned!
<p class="text-danger">{{ $errors->first('minutes') }}</p>
@endif
</div>
<div class="col-6">
{{ Form::label('level', __('flights.level')) }} ({{config('phpvms.internal_units.altitude')}})
@if(!empty($pirep) && $pirep->read_only)
<p>{{ $pirep->level }}</p>
@else
<div class="input-group input-group-sm form-group">
{{ Form::number('level', null, [
'class' => 'form-control',
'min' => '0',
'step' => '0.01',
'readonly' => (!empty($pirep) && $pirep->read_only),
]) }}
</div>
<p class="text-danger">{{ $errors->first('level') }}</p>
@endif
</div>
</div>
</div>
</div>

View File

@ -98,8 +98,8 @@ and being mindful of the rivets bindings
<td>{ pirep.aircraft.name }</td>
<td>{ pirep.position.altitude }</td>
<td>{ pirep.position.gs }</td>
<td>{ pirep.position.distance.{{setting('units.distance')}} } /
{ pirep.planned_distance.{{setting('units.distance')}} }
<td>{ pirep.position.distance.{{setting('units.distance')}} | fallback 0 } /
{ pirep.planned_distance.{{setting('units.distance')}} | fallback 0 }
</td>
<td>{ pirep.status_text }</td>
</tr>

42
tests/AirlineTest.php Normal file
View File

@ -0,0 +1,42 @@
<?php
use App\Services\AirlineService;
class AirlineTest extends TestCase
{
protected $airlineSvc;
public function setUp(): void
{
parent::setUp();
$this->addData('base');
$this->airlineSvc = app(AirlineService::class);
}
/**
* Try deleting an airline which has flights/other assets that exist
*/
public function testDeleteAirlineWithFlight()
{
$airline = factory(App\Models\Airline::class)->create();
factory(App\Models\Flight::class)->create([
'airline_id' => $airline->id,
]);
$this->assertFalse($this->airlineSvc->canDeleteAirline($airline));
}
/**
* Try deleting an airline with existing PIREPs
*/
public function testDeleteAirlineWithPirep()
{
$airline = factory(App\Models\Airline::class)->create();
factory(App\Models\Pirep::class)->create([
'airline_id' => $airline->id,
]);
$this->assertFalse($this->airlineSvc->canDeleteAirline($airline));
}
}