Add more API resources; user bid management and tests #35 #36

This commit is contained in:
Nabeel Shahzad 2017-12-12 16:58:27 -06:00
parent 5b25a464ba
commit 248a8d1488
27 changed files with 470 additions and 166 deletions

View File

@ -26,6 +26,7 @@
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="LongInheritanceChainInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpExpressionResultUnusedInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpIncludeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpSignatureMismatchDuringInheritanceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">

View File

@ -6,12 +6,21 @@ use \Illuminate\Support\Facades\Facade;
class Utils extends Facade
{
protected static function getFacadeAccessor()
{
return 'utils';
}
/**
* Returns a 40 character API key that a user can use
* @return string
*/
public static function generateApiKey()
{
$key = sha1(time() . mt_rand());
return $key;
}
/**
* Convert seconds to an array of hours, minutes, seconds
* @param $seconds

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use Log;
use Illuminate\Http\Request;
use App\Http\Controllers\AppBaseController;
@ -25,6 +26,10 @@ class FlightController extends AppBaseController
return new FlightResource($flight);
}
/**
* @param Request $request
* @return mixed
*/
public function search(Request $request)
{
try {

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\AppBaseController;
use App\Repositories\UserRepository;
use App\Models\UserBid;
use App\Http\Resources\Flight as FlightResource;
use App\Http\Resources\User as UserResource;
use App\Http\Resources\UserBid as UserBidResource;
class UserController extends AppBaseController
{
protected $userRepo;
public function __construct(UserRepository $userRepo)
{
$this->userRepo = $userRepo;
}
/**
* Return the profile for the currently auth'd user
*/
public function index(Request $request)
{
UserResource::withoutWrapping();
return new UserResource($request->user);
}
/**
* Get the profile for the passed-in user
*/
public function get($id)
{
UserResource::withoutWrapping();
return new UserResource($this->userRepo->find($id));
}
/**
* Return all of the bids for the passed-in user
*/
public function bids($id)
{
$flights = UserBid::where(['user_id' => $id])->get()
->pluck('flight');
return FlightResource::collection($flights);
}
}

View File

@ -6,7 +6,6 @@
namespace App\Http\Middleware;
use Auth;
use Cache;
use Closure;
use App\Models\User;
@ -28,16 +27,8 @@ class ApiAuth
// Try to find the user via API key. Cache this lookup
$api_key = $request->header('Authorization');
$user = User::where('apikey', $api_key)->first();
/*$user = Cache::remember(
config('cache.keys.USER_API_KEY.key') . $api_key,
config('cache.keys.USER_API_KEY.time'),
function () use ($api_key) {
return User::where('apikey', $api_key)->first();
}
);*/
if(!$user) {
$user = User::where('api_key', $api_key)->first();
if($user === null) {
return $this->unauthorized();
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Aircraft extends Resource
{
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Airline extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'icao' => $this->icao,
'iata' => $this->iata,
'name' => $this->name,
'country' => $this->country,
'logo' => $this->logo,
#'active' => $this->active,
#'created_at' => $this->created_at,
#'updated_at' => $this->updated_at,
];
}
}

View File

@ -6,12 +6,6 @@ use Illuminate\Http\Resources\Json\Resource;
class Airport extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);

View File

@ -6,12 +6,6 @@ use Illuminate\Http\Resources\Json\Resource;
class Flight extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
@ -29,8 +23,10 @@ class Flight extends Resource
'flight_time' => $this->flight_time,
'notes' => $this->notes,
'active' => $this->active,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'subfleet' => Subfleet::collection($this->subfleets),
#'created_at' => $this->created_at,
#'updated_at' => $this->updated_at,
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Rank extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
#'auto_approve_acars' => $this->auto_approve_acars,
#'auto_approve_manual' => $this->auto_approve_manual,
#'auto_promote' => $this->auto_promote,
#'created_at' => $this->created_at,
#'updated_at' => $this->updated_at,
];
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Subfleet extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'airline_id' => $this->airline_id,
'name' => $this->name,
'type' => $this->type,
'fuel_type' => $this->fuel_type,
'cargo_capacity' => $this->cargo_capacity,
'fuel_capacity' => $this->fuel_capacity,
'gross_weight' => $this->gross_weight,
'aircraft' => Aircraft::collection($this->aircraft),
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class User extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'apikey' => $this->apikey,
'rank_id' => $this->rank_id,
'home_airport' => $this->home_airport_id,
'curr_airport' => $this->curr_airport_id,
'last_pirep_id' => $this->last_pirep_id,
'flights' => $this->flight,
'flight_time' => $this->flight_time,
'balance' => $this->balance,
'timezone' => $this->timezone,
'active' => $this->active,
'airline' => Airline::make($this->airline),
'bids' => UserBid::collection($this->bids),
'rank' => Rank::make($this->rank),
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserBid extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'flight_id' => $this->flight_id,
'flight' => Flight::collection($this->whenLoaded('flight'))
#'created_at' => $this->created_at,
#'updated_at' => $this->updated_at,
];
}
}

View File

@ -4,51 +4,38 @@ namespace App\Models;
use Eloquent as Model;
/**
* Class Aircraft
*
* @package App\Models
* @version June 9, 2017, 1:06 am UTC
*/
class Aircraft extends Model
{
public $table = 'aircraft';
protected $dates = ['deleted_at'];
public $fillable
= [
'subfleet_id',
'airport_id',
'name',
'registration',
'tail_number',
'active',
];
public $fillable = [
'subfleet_id',
'airport_id',
'name',
'registration',
'tail_number',
'active',
];
/**
* The attributes that should be casted to native types.
*
* @var array
*/
protected $casts
= [
'subfleet_id' => 'string',
'name' => 'string',
'registration' => 'string',
'active' => 'boolean',
];
protected $casts = [
'active' => 'boolean',
];
/**
* Validation rules
*
* @var array
*/
public static $rules
= [
'name' => 'required',
'active' => '',
];
public static $rules = [
'name' => 'required',
];
/**
* foreign keys

View File

@ -6,11 +6,6 @@ use Eloquent as Model;
use App\Models\Traits\HashId;
/**
* Class Flight
*
* @package App\Models
*/
class Flight extends Model
{
use HashId;
@ -64,6 +59,11 @@ class Flight extends Model
'arr_airport_id' => 'required',
];
public function getFlightIdIataAttribute($value)
{
}
/**
* Relationship
*/

View File

@ -33,6 +33,11 @@ class Subfleet extends Model
'gross_weight' => 'double',
];
public function aircraft()
{
return $this->hasMany('App\Models\Aircraft', 'subfleet_id');
}
public function airline()
{
return $this->belongsTo('App\Models\Airline', 'airline_id');

View File

@ -9,69 +9,41 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Contracts\Auth\CanResetPassword;
/**
* App\User
*
* @property integer
* $id
* @property string
* $name
* @property string
* $email
* @property string
* $password
* @property string
* $remember_token
* @property \Carbon\Carbon
* $created_at
* @property \Carbon\Carbon
* $updated_at
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[]
* $notifications
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[]
* $unreadNotifications
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereId($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereName($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereEmail($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\App\Models\User
* whereUpdatedAt($value)
* @mixin \Eloquent
* @property integer $id
* @property string $name
* @property string $email
* @property string $password
* @property string $api_key
* @property string $flights
* @property string $flight_time
* @property string $remember_token
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @mixin \Illuminate\Notifications\Notifiable
* @mixin \Zizaco\Entrust\Traits\EntrustUserTrait
*/
class User extends Authenticatable
{
use Notifiable;
use EntrustUserTrait;
//use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable
= [
'name',
'email',
'password',
'airline_id',
'home_airport_id',
'curr_airport_id',
'rank_id',
'timezone',
'active',
];
public $table = 'users';
protected $fillable = [
'name',
'email',
'password',
'airline_id',
'home_airport_id',
'curr_airport_id',
'rank_id',
'timezone',
'active',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
@ -84,32 +56,13 @@ class User extends Authenticatable
'balance' => 'double',
];
/**
* Validation rules
*
* @var array
*/
public static $rules = [
];
/**
* Returns a 40 character API key that a user can use
* @return string
*/
public static function generateApiKey()
{
$key = sha1(time() . mt_rand());
return $key;
}
public function getPilotIdAttribute($value)
{
$length = setting('pilots.id_length');
return $this->airline->icao . str_pad($this->id, $length, '0', STR_PAD_LEFT);
}
public function gravatar()
public function getGravatarAttribute($value)
{
$size = 80;
$default = 'https://en.gravatar.com/userimage/12856995/7c7c1da6387853fea65ff74983055386.png';
@ -136,7 +89,7 @@ class User extends Authenticatable
return $this->belongsTo('App\Models\Airport', 'curr_airport_id');
}
public function flights()
public function bids()
{
return $this->hasMany('App\Models\UserBid', 'user_id');
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Created by IntelliJ IDEA.
* User: nshahzad
* Date: 12/12/17
* Time: 2:48 PM
*/
namespace App\Services;
use Log;
use App\Models\Flight;
use App\Models\User;
use App\Models\UserBid;
class FlightService extends BaseService
{
/**
* Allow a user to bid on a flight. Check settings and all that good stuff
* @param Flight $flight
* @param User $user
* @return UserBid
*/
public function addBid(Flight $flight, User $user): UserBid
{
# If it's already been bid on, then it can't be bid on again
if($flight->has_bid && setting('bids.disable_flight_on_bid')) {
Log.info($flight->id . ' already has a bid, skipping');
return null;
}
# See if we're allowed to have multiple bids or not
if(!setting('bids.allow_multiple_bids')) {
$user_bids = UserBid::where(['user_id' => $user->id])->first();
if($user_bids) {
Log.info('User "' . $user->id . '" already has bids, skipping');
return null;
}
}
# See if this user has this flight bid on already
$bid_data = [
'user_id' => $user->id,
'flight_id' => $flight->id
];
$user_bid = UserBid::where($bid_data)->first();
if($user_bid) {
return $user_bid;
}
$user_bid = UserBid::create($bid_data);
$flight->has_bid = true;
$flight->save();
return $user_bid;
}
/**
* Remove a bid from a given flight
* @param Flight $flight
* @param User $user
* @throws Exception
*/
public function removeBid(Flight $flight, User $user)
{
$user_bid = UserBid::where([
'flight_id' => $flight->id, 'user_id' => $user->id
])->first();
if($user_bid) {
$user_bid->forceDelete();
}
$flight->has_bid = false;
$flight->save();
}
}

View File

@ -3,6 +3,7 @@
namespace App\Services;
use App\Events\UserRegistered;
use App\Facades\Utils;
use App\Models\User;
use App\Models\Rank;
use App\Models\Role;
@ -15,32 +16,32 @@ use Illuminate\Support\Facades\Hash;
class UserService extends BaseService
{
public function adjustFlightCount(User &$pilot, int $count): User
public function adjustFlightCount(User $user, int $count): User
{
$pilot->refresh();
$pilot->flights = $pilot->flights + $count;
$pilot->save();
$user->refresh();
$user->flights += $count;
$user->save();
event(new UserStateChanged($pilot));
event(new UserStateChanged($user));
return $pilot;
return $user;
}
public function adjustFlightHours(User &$pilot, int $hours): User
public function adjustFlightHours(User $user, int $hours): User
{
$pilot->refresh();
$pilot->flight_time = $pilot->flight_time + $hours;
$pilot->save();
$user->refresh();
$user->flight_time += $hours;
$user->save();
event(new UserStateChanged($pilot));
event(new UserStateChanged($user));
return $pilot;
return $user;
}
public function calculatePilotRank(User &$pilot): User
public function calculatePilotRank(User $user): User
{
$pilot->refresh();
$pilot_hours = $pilot->flight_time / 3600;
$user->refresh();
$pilot_hours = $user->flight_time / 3600;
# TODO: Cache
$ranks = Cache::remember(
@ -54,23 +55,28 @@ class UserService extends BaseService
if($rank->hours > $pilot_hours) {
break;
} else {
$pilot->rank_id = $rank->id;
$user->rank_id = $rank->id;
}
}
$pilot->save();
$user->save();
event(new UserStateChanged($pilot));
event(new UserStateChanged($user));
return $pilot;
return $user;
}
/**
* Register a pilot
* @param array $data
* @return mixed
*/
public function createPilot(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'apikey' => User::generateApiKey(),
'api_key' => Utils::generateApiKey(),
'airline_id' => $data['airline'],
'home_airport_id' => $data['home_airport'],
'curr_airport_id' => $data['home_airport'],

View File

@ -18,7 +18,7 @@ class CreateUsersTable extends Migration
$table->string('name')->nullable();
$table->string('email')->unique();
$table->string('password');
$table->string('apikey', 40)->nullable();
$table->string('api_key', 40)->nullable();
$table->unsignedInteger('airline_id');
$table->unsignedInteger('rank_id');
$table->string('home_airport_id', 5)->nullable();
@ -34,7 +34,7 @@ class CreateUsersTable extends Migration
$table->softDeletes();
$table->index('email');
$table->index('apikey');
$table->index('api_key');
});
// Create table for storing roles

View File

@ -65,7 +65,7 @@ class CreateSettingsTable extends Migration
'key' => 'bids.disable_flight_on_bid',
'value' => true,
'type' => 'boolean',
'description' => 'When a flight is bid on, should the flight be shown',
'description' => 'When a flight is bid on, no one else can bid on it',
],
[
'order' => 21,

View File

@ -13,6 +13,7 @@ users:
name: Admin User
email: admin@phpvms.net
password: admin
api_key: testadminapikey
airline_id: 1
rank_id: 1
home_airport_id: KAUS
@ -26,6 +27,7 @@ users:
name: Carla Walters
email: carla.walters68@example.com
password: admin
api_key: testuserapikey1
airline_id: 1
rank_id: 1
home_airport_id: KJFK
@ -39,6 +41,7 @@ users:
name: Raymond Pearson
email: raymond.pearson56@example.com
password: admin
api_key: testuserapikey2
airline_id: 1
rank_id: 1
home_airport_id: KJFK

View File

@ -1,7 +1,7 @@
<div class="row">
<div class="form-group col-sm-12">
{{--<div class="avatar">
<img src="{!! $pirep->pilot->gravatar() !!}" />
<img src="{!! $pirep->pilot->gravatar !!}" />
</div>--}}
Filed By: <a href="{!! route('admin.users.edit', [$pirep->pilot->id]) !!}" target="_blank">
{!! $pirep->pilot->pilot_id !!} {!! $pirep->pilot->name !!}

View File

@ -21,7 +21,7 @@
</h3>
<div class="photo-container">
<img class="rounded-circle"
src="{!! $user->gravatar() !!}">
src="{!! $user->gravatar !!}">
</div>
</div>
<div class="content content-center">

View File

@ -13,8 +13,8 @@ use Illuminate\Http\Request;
|
*/
Route::group([], function () {
Route::group([], function ()
{
Route::match(['get'], 'status', 'BaseController@status');
Route::match(['get'], 'airports/{id}', 'AirportController@get');
@ -24,4 +24,10 @@ Route::group([], function () {
Route::match(['get'], 'flights/{id}', 'FlightController@get');
Route::match(['get'], 'pirep/{id}', 'PirepController@get');
# This is the info of the user whose token is in use
Route::match(['get'], 'user', 'UserController@index');
#Route::match(['get'], 'user/bids', 'UserController@index');
Route::match(['get'], 'users/{id}', 'UserController@get');
Route::match(['get'], 'users/{id}/bids', 'UserController@bids');
});

View File

@ -1,5 +1,8 @@
<?php
use App\Services\FlightService;
use App\Models\Flight;
use App\Models\User;
class FlightTest extends TestCase
{
@ -7,6 +10,8 @@ class FlightTest extends TestCase
{
parent::setUp();
$this->addData('base');
$this->flightSvc = app(FlightService::class);
}
public function addFlight()
@ -18,15 +23,22 @@ class FlightTest extends TestCase
$flight->arr_airport_id = 'KJFK';
$flight->save();
# subfleet ID is in the base.yml
$flight->subfleets()->syncWithoutDetaching([1]);
return $flight->id;
}
public function testGetFlight()
{
$flight_id = $this->addFlight();
$this->get('/api/flights/'.$flight_id, self::$auth_headers)
->assertStatus(200)
->assertJson(['dpt_airport_id' => 'KAUS']);
$req = $this->get('/api/flights/'.$flight_id, self::$auth_headers);
$req->assertStatus(200);
$body = $req->json();
$this->assertEquals($flight_id, $body['id']);
$this->assertEquals('KAUS', $body['dpt_airport_id']);
$this->assertEquals('KJFK', $body['arr_airport_id']);
$this->get('/api/flights/INVALID', self::$auth_headers)
->assertStatus(404);
@ -44,4 +56,62 @@ class FlightTest extends TestCase
$req = $this->get('/api/flights/search?' . $query, self::$auth_headers);
$req->assertStatus(200);
}
/**
* Add/remove a bid, test the API, etc
* @throws \App\Services\Exception
*/
public function testBids()
{
$flight_id = $this->addFlight();
$user = User::find(1);
$flight = Flight::find($flight_id);
$bid = $this->flightSvc->addBid($flight, $user);
$this->assertEquals(1, $bid->user_id);
$this->assertEquals($flight_id, $bid->flight_id);
$this->assertTrue($flight->has_bid);
# Refresh
$flight = Flight::find($flight_id);
$this->assertTrue($flight->has_bid);
# Query the API and see that the user has the bids
# And pull the flight details for the user/bids
$req = $this->get('/api/user', self::$auth_headers);
$req->assertStatus(200);
$body = $req->json();
$this->assertEquals(1, sizeof($body['bids']));
$this->assertEquals($flight_id, $body['bids'][0]['flight_id']);
$req = $this->get('/api/users/1/bids', self::$auth_headers);
$body = $req->json();
$req->assertStatus(200);
$this->assertEquals($flight_id, $body[0]['id']);
# Now remove the flight and check API
$this->flightSvc->removeBid($flight, $user);
$flight = Flight::find($flight_id);
$this->assertFalse($flight->has_bid);
$user = User::find(1);
$bids = $user->bids()->get();
$this->assertTrue($bids->isEmpty());
$req = $this->get('/api/user', self::$auth_headers);
$req->assertStatus(200);
$body = $req->json();
$this->assertEquals(0, sizeof($body['bids']));
$req = $this->get('/api/users/1/bids', self::$auth_headers);
$req->assertStatus(200);
$body = $req->json();
$this->assertEquals(0, sizeof($body));
}
}

View File

@ -13,8 +13,10 @@ users:
name: Admin User
email: admin@phpvms.net
password: admin
apikey: testapikey
api_key: testapikey
airline_id: 1
home_airport_id: KAUS
curr_airport_id: KAUS
rank_id: 1
created_at: now
updated_at: now
@ -70,6 +72,11 @@ aircraft:
name: Boeing 777-200
registration: NC20
tail_number: 20
- id: 3
subfleet_id: 1
name: Boeing 747-400-PW
registration: PW744
tail_number: 207X
fares:
- id: 1
@ -133,6 +140,6 @@ flights:
- id: 1
airline_id: 1
flight_number: 100
dpt_airport_id: 1
arr_airport_id: 2
dpt_airport_id: KAUS
arr_airport_id: KJFK
route: KAUS KJFK