diff --git a/app/Console/Commands/Structures/ExecuteFetchAllianceAssetsCommand.php b/app/Console/Commands/Structures/ExecuteFetchAllianceAssetsCommand.php new file mode 100644 index 000000000..4bf841ea0 --- /dev/null +++ b/app/Console/Commands/Structures/ExecuteFetchAllianceAssetsCommand.php @@ -0,0 +1,46 @@ +onQueue('default'); + + return 0; + } +} diff --git a/app/Console/Commands/Structures/ExecuteFetchAllianceStructuresCommand.php b/app/Console/Commands/Structures/ExecuteFetchAllianceStructuresCommand.php new file mode 100644 index 000000000..7b69b8e78 --- /dev/null +++ b/app/Console/Commands/Structures/ExecuteFetchAllianceStructuresCommand.php @@ -0,0 +1,46 @@ +onQueue('default'); + + return 0; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 2d32dbe8e..47458cacc 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -50,9 +50,6 @@ class Kernel extends ConsoleKernel //Horizon Graph Schedule $schedule->command('horizon:snapshot')->everyFiveMinutes(); - //Test rung to help figure out what is going on. - //$schedule->command('inspire')->everyMinute(); - /** * Purge Data Schedule */ @@ -96,6 +93,23 @@ class Kernel extends ConsoleKernel $schedule->job(new UpdateMiningTaxesLateInvoices15th) ->timezone('UTC') ->monthlyOn(15, '16:00'); + + /** + * Alliance Structure and Assets Schedule + */ + $schedule->job(new FetchAllianceStructures) + ->timezone('UTC') + ->dailyAt('21:00'); + $schedule->job(new FetchAllianceAssets) + ->timezone('UTC') + ->hourlyAt('15'); + $schedule->job(new PurgeAllianceStructures) + ->timezone('UTC') + ->monthlyOn(2, '14:00'); + $schedule->job(new PurgeAllianceAssets) + ->timezone('UTC') + ->monthlyOn(2, '15:00'); + } /** diff --git a/app/Jobs/Commands/Assets/FetchAllianceAssets.php b/app/Jobs/Commands/Assets/FetchAllianceAssets.php new file mode 100644 index 000000000..da26d6863 --- /dev/null +++ b/app/Jobs/Commands/Assets/FetchAllianceAssets.php @@ -0,0 +1,150 @@ +GetRefreshToken($config['primary']); + //Create the esi authentication container + $esi = $esiHelper->SetupEsiAuthentication($token); + + //Check the esi scope + if(!$esiHelpeer->HaveEsiScope($config['primary'], 'esi-assets.read_corporation_assets.v1')) { + Log::critical("Scope check failed in FetchAllianceAssets for esi-assets.read_corporation_assets.v1"); + } + + //Set the current page + $currentPage = 1; + //Set our default pages + $totalPages = 1; + + do { + //Attempt to get the assets + $assets = $esi->page($currentPage) + ->invoke('get', '/corporations/{corporation_id}/assets/', [ + 'corporation_id' => $corpId, + ]); + + //If on the first page, then update the total number of pages + if($currentPage == 1) { + $totalPages = $assets->pages; + } + + //For each asset retrieved, let's process it. + foreach($assets as $a) { + ProcessAllianceAssets::dispatch($a)->onQueue('default'); + } + + //Increment the current page + $currentPage++; + } while($currentPage <= $totalPages); + + } + + /** + * The job failed to process + * @param Exception $exception + * @return void + */ + public function failed($exception) { + if(!exception instanceof RequestFailedException) { + //If not a failure due to ESI, then log it. Otherwise, + //deduce why the exception occurred. + Log::critical($exception); + } + + if ((is_object($exception->getEsiResponse()) && (stristr($exception->getEsiResponse()->error, 'Too many errors') || stristr($exception->getEsiResponse()->error, 'This software has exceeded the error limit for ESI'))) || + (is_string($exception->getEsiResponse()) && (stristr($exception->getEsiResponse(), 'Too many errors') || stristr($exception->getEsiResponse(), 'This software has exceeded the error limit for ESI')))) { + + //We have hit the error rate limiter, wait 120 seconds before releasing the job back into the queue. + Log::info('FetchMiningTaxesObservers has hit the error rate limiter. Releasing the job back into the wild in 2 minutes.'); + $this->release(120); + } else { + $errorCode = $exception->getEsiResponse()->getErrorCode(); + + switch($errorCode) { + case 400: //Bad Request + Log::critical("Bad request has occurred in FetchMiningTaxesObservers. Job has been discarded"); + break; + case 401: //Unauthorized Request + Log::critical("Unauthorized request has occurred in FetchMiningTaxesObservers at " . Carbon::now()->toDateTimeString() . ".\r\nCancelling the job."); + break; + case 403: //Forbidden + Log::critical("FetchMiningTaxesObservers has incurred a forbidden error. Cancelling the job."); + break; + case 420: //Error Limited + Log::warning("Error rate limit occurred in FetchMiningTaxesObservers. Restarting job in 120 seconds."); + $this->release(120); + break; + case 500: //Internal Server Error + Log::critical("Internal Server Error for ESI in FetchMiningTaxesObservers. Attempting a restart in 120 seconds."); + $this->release(120); + break; + case 503: //Service Unavailable + Log::critical("Service Unavailabe for ESI in FetchMiningTaxesObservers. Releasing the job back to the queue in 30 seconds."); + $this->release(30); + break; + case 504: //Gateway Timeout + Log::critical("Gateway timeout in FetchMiningTaxesObservers. Releasing the job back to the queue in 30 seconds."); + $this->release(30); + break; + case 201: //Good response code + $this->delete(); + break; + //If no code is given, then log and break out of switch. + default: + Log::warning("No response code received from esi call in FetchMiningTaxesObservers.\r\n"); + $this->delete(); + break; + } + } + } + + /** + * Tags for jobs + * + * @var array + */ + public function tags() { + return ['FetchAllianceAssets', 'AllianceStructures', 'Asset']; + } +} diff --git a/app/Jobs/Commands/JumpBridges/FetchJumpBridges.php b/app/Jobs/Commands/Assets/ProcessAllianceAssets.php similarity index 86% rename from app/Jobs/Commands/JumpBridges/FetchJumpBridges.php rename to app/Jobs/Commands/Assets/ProcessAllianceAssets.php index 09ac0ff8e..1ec61995c 100644 --- a/app/Jobs/Commands/JumpBridges/FetchJumpBridges.php +++ b/app/Jobs/Commands/Assets/ProcessAllianceAssets.php @@ -1,6 +1,6 @@ onQueue('default'); } } diff --git a/app/Jobs/Commands/JumpBridges/PurgeJumpBridgeStructures.php b/app/Jobs/Commands/JumpBridges/PurgeJumpBridgeStructures.php deleted file mode 100644 index 0f1063a1a..000000000 --- a/app/Jobs/Commands/JumpBridges/PurgeJumpBridgeStructures.php +++ /dev/null @@ -1,34 +0,0 @@ -HaveEsiScope($config['primary'], 'esi-universe.read_structures.v1'); + $corpStructureScope = $esiHelper->HaveEsiScope($config['primary'], 'esi-corporations.read_structures.v1'); + + //Check scopes + if($structureScope == false || $corpStructureScope == false) { + if($structureScope == false) { + Log::critical("Scope check for esi-universe.read_structures.v1 has failed."); + } + if($corpStructureScope == false) { + Log::critical("Scope check for esi-corporations.read_structures.v1 has failed."); + } + + return -1; + } + + //Get the refresh token from the database + $token = $esiHelper->GetRefreshToken($config['primary']); + //Create the esi authentication container + $esi = $esiHelper->SetupEsiAuthentication($token); + + //Set the current page + $currentPage = 1; + //Set our default pages + $totalPages = 1; + + do { + //Attempt to get the entire page worth of structures + $structures = $esi->page($currentPage) + ->invoke('get', '/corporations/{corporation_id}/structures/', [ + 'corporation_id' => $corpId, + ]); + + //If on the first page, then update the total number of pages + if($currentPage == 1) { + $totalPages = $structures->pages; + } + + //For each asset retrieved, let's process it. + foreach($structures as $s) { + ProcessAllianceStructures::dispatch($s)->onQueue('default'); + } + + //Increment the current page + $currentPage++; + } while($currentPage <= $totalPages); + + } + + /** + * The job failed to process + * @param Exception $exception + * @return void + */ + public function failed($exception) { + if(!exception instanceof RequestFailedException) { + //If not a failure due to ESI, then log it. Otherwise, + //deduce why the exception occurred. + Log::critical($exception); + } + + if ((is_object($exception->getEsiResponse()) && (stristr($exception->getEsiResponse()->error, 'Too many errors') || stristr($exception->getEsiResponse()->error, 'This software has exceeded the error limit for ESI'))) || + (is_string($exception->getEsiResponse()) && (stristr($exception->getEsiResponse(), 'Too many errors') || stristr($exception->getEsiResponse(), 'This software has exceeded the error limit for ESI')))) { + + //We have hit the error rate limiter, wait 120 seconds before releasing the job back into the queue. + Log::info('FetchMiningTaxesObservers has hit the error rate limiter. Releasing the job back into the wild in 2 minutes.'); + $this->release(120); + } else { + $errorCode = $exception->getEsiResponse()->getErrorCode(); + + switch($errorCode) { + case 400: //Bad Request + Log::critical("Bad request has occurred in FetchMiningTaxesObservers. Job has been discarded"); + break; + case 401: //Unauthorized Request + Log::critical("Unauthorized request has occurred in FetchMiningTaxesObservers at " . Carbon::now()->toDateTimeString() . ".\r\nCancelling the job."); + break; + case 403: //Forbidden + Log::critical("FetchMiningTaxesObservers has incurred a forbidden error. Cancelling the job."); + break; + case 420: //Error Limited + Log::warning("Error rate limit occurred in FetchMiningTaxesObservers. Restarting job in 120 seconds."); + $this->release(120); + break; + case 500: //Internal Server Error + Log::critical("Internal Server Error for ESI in FetchMiningTaxesObservers. Attempting a restart in 120 seconds."); + $this->release(120); + break; + case 503: //Service Unavailable + Log::critical("Service Unavailabe for ESI in FetchMiningTaxesObservers. Releasing the job back to the queue in 30 seconds."); + $this->release(30); + break; + case 504: //Gateway Timeout + Log::critical("Gateway timeout in FetchMiningTaxesObservers. Releasing the job back to the queue in 30 seconds."); + $this->release(30); + break; + case 201: //Good response code + $this->delete(); + break; + //If no code is given, then log and break out of switch. + default: + Log::warning("No response code received from esi call in FetchMiningTaxesObservers.\r\n"); + $this->delete(); + break; + } + } + } + + public function tags() { + return ['FetchAllianceStructures', 'AllianceStructures', 'Structures']; + } +} diff --git a/app/Jobs/Commands/Structures/ProcessAllianceStructures.php b/app/Jobs/Commands/Structures/ProcessAllianceStructures.php new file mode 100644 index 000000000..94ae97ea2 --- /dev/null +++ b/app/Jobs/Commands/Structures/ProcessAllianceStructures.php @@ -0,0 +1,203 @@ +structure = $s; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + /** + * Update the structure if it already exists, or add the structure if it doesn't exist in the database + */ + if(Structure::where(['structure_id' => $this->structure->structure_id])->count() > 0) { + $this->UpdateStructure($this->structure); + } else { + $this->SaveNewStructure($this->structure); + } + } + + /** + * Set the tags for the job + * + * @var array + */ + public function tags() { + return ['ProcessAllianceStructures', 'AllianceStructures', 'Structures']; + } + + private SaveNewStructure($structure) { + //Declare variables + $lookup = new LookupHelper; + $esiHelper = new Esi; + + //Get the solar system name + $solarName = $lookup->SolarSystemIdToName($structure->system_id); + + $s = new Structure; + $s->structure_id = $structure->structure_id; + $s->structure_name = $structure->name; + $s->solar_system_id = $structure->system_id; + $s->solar_system_name = $solarName; + $s->type_id = $structure->type_id; + $s->type_name = $lookup->StructureTypeIdToName($structure->type_id); + $s->corporation_id = $structure->corporation_id; + if(isset($structure->services)) { + $s->services = true; + } else { + $s->services = false; + } + $s->state = $structure->state; + if(isset($structre->state_timer_start)) { + $s->state_timer_start = $esiHelper->DecodeDate($structure->state_timer_start); + } + if(isset($structure->state_timer_end)) { + $s->state_timer_end = $esiHelper->DecodeDate($structure->state_timer_end); + } + if(isset($structure->fuel_expires)) { + $s->fuel_expires = $esiHelper->DecodeDate($structure->fuel_expires); + } + $s->profile_id = $structure->profile_id; + if(isset($structure->next_reinforce_apply)) { + $s->next_reinforce_apply = $structure->next_reinforce_apply; + } + if(isset($structure->next_reinforce_hour)) { + $s->next_reinforce_hour = $structure->next_reinforce_hour; + } + $s->reinforce_hour = $structure->reinforce_hour; + if(isset($structure->unanchors_at)) { + $s->unanchors_at = $esiHelper->DecodeDate($s->unanchors_at); + } + $s->save(); + + //If there are services on the structure, then save the service and the structure id in the database table. + if($s->services == true) { + foreach($structure->services as $service) { + $serv = new Service; + $serv->structure_id = $structure->structure_id; + $serv->name = $service->name; + $serv->state = $service->state; + } + } + } + + private UpdateStructure($structure) { + if(isset($structure->corporation_id)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'corporation_id' => $structure->corporation_id, + ]); + } + if(isset($structure->state)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'state' => $structure->state, + ]); + } + if(isset($structure->state_timer_start)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'state_timer_start' => $structure->state_timer_start, + ]); + } + if(isset($structure->state_timer_end)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'state_timer_end' => $structure->state_timer_end, + ]); + } + if(isset($structure->fuel_expires)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'fuel_expires' => $structure->fuel_expires, + ]); + } + if(isset($structure->profile_id)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'profile_id' => $structure->profile_id, + ]); + } + if(isset($structure->next_reinforce_apply)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'next_reinforce_apply' => $structure->next_reinforce_apply, + ]); + } + if(isset($structure->next_reinforce_hour)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'next_reinforce_hour' => $structure->next_reinforce_hour, + ]); + } + if(isset($structure->reinforce_hour)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'reinforce_hour' => $structure->reinforce_hour, + ]); + } + if(isset($structure->unanchors_at)) { + Structure::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'unanchors_at' => $structure->unanchors_at, + ]); + } + + if(Service::where(['structure_id' => $structure->structure_id])->count() > 0) { + foreach($structure->services as $service) { + Service::where([ + 'structure_id' => $structure->structure_id, + ])->update([ + 'state' => $service->state, + ]); + } + } + } +} diff --git a/app/Jobs/Commands/JumpBridges/FetchLiquidOzoneAssets.php b/app/Jobs/Commands/Structures/PurgeAllianceStructures.php similarity index 58% rename from app/Jobs/Commands/JumpBridges/FetchLiquidOzoneAssets.php rename to app/Jobs/Commands/Structures/PurgeAllianceStructures.php index 86a6d0024..bde531466 100644 --- a/app/Jobs/Commands/JumpBridges/FetchLiquidOzoneAssets.php +++ b/app/Jobs/Commands/Structures/PurgeAllianceStructures.php @@ -1,14 +1,22 @@ onQueue('default'); } } diff --git a/app/Library/Helpers/LookupHelper.php b/app/Library/Helpers/LookupHelper.php index 943cff7c5..9b4ec2119 100644 --- a/app/Library/Helpers/LookupHelper.php +++ b/app/Library/Helpers/LookupHelper.php @@ -32,6 +32,42 @@ class LookupHelper { $this->esi = $esiHelper->SetupEsiAuthentication(); } + public function StructureTypeIdToName($typeId) { + $structureTypes = [ + 35841 => 'Ansiblex Jump Gate', + 35840 => 'Pharolux Cyno Beacon', + 37534 => 'Tenebrex Cyno Jammer', + 35834 => 'Keepstar', + 35833 => 'Fortizar', + 35832 => 'Astrahus', + 35836 => 'Tatara', + 35835 => 'Athanor', + 35827 => 'Sotiyo', + 35826 => 'Azbel', + 35825 => 'Raitaru', + ] + + return $structureTypes[$typeId]; + } + + public function StructureNameToTypeId($name) { + $structureTypes = [ + 'Ansiblex Jump Gate' => 35841, + 'Pharolux Cyno Beacon' => 35840, + 'Tenebrex Cyno Jammer' => 37534, + 'Keepstar' => 35834, + 'Fortizar' => 35833, + 'Astrahus' => 35832, + 'Tatara' => 35836, + 'Athanor' => 35835, + 'Sotiyo' => 35827, + 'Azbel' => 35826, + 'Raitaru' => 35825, + ]; + + return $structureTypes[$name]; + } + public function ItemNameToId($itemName) { $item = ItemLookup::where([ 'name' => $itemName, diff --git a/app/Models/Structure/Asset.php b/app/Models/Structure/Asset.php index 26c320624..b6fcd2c67 100644 --- a/app/Models/Structure/Asset.php +++ b/app/Models/Structure/Asset.php @@ -34,6 +34,7 @@ class Asset extends Model 'location_type', 'quantity', 'type_id', + 'created_at', 'updated_at', ]; diff --git a/app/Models/Structure/Service.php b/app/Models/Structure/Service.php index 3838dda86..0d37f769d 100644 --- a/app/Models/Structure/Service.php +++ b/app/Models/Structure/Service.php @@ -10,7 +10,7 @@ class Service extends Model public $table = 'alliance_services'; //Timestamps - public $timestamps = false; + public $timestamps = true; //Primary Key public $primaryKey = 'id'; @@ -24,6 +24,8 @@ class Service extends Model 'structure_id', 'name', 'state', + 'created_at', + 'updated_at', ]; public function structure() { diff --git a/app/Models/Structure/Structure.php b/app/Models/Structure/Structure.php index d766d7386..680fef2c6 100644 --- a/app/Models/Structure/Structure.php +++ b/app/Models/Structure/Structure.php @@ -31,6 +31,7 @@ class Structure extends Model 'solar_system_id', 'solar_system_name', 'type_id', + 'type_name', 'corporation_id', 'services', //True or false on whether it has services which are held in a different table 'state', @@ -38,15 +39,12 @@ class Structure extends Model 'state_timer_end', 'fuel_expires', 'profile_id', - 'position_x', - 'position_y', - 'position_z', 'next_reinforce_apply', 'next_reinforce_hour', - 'next_reinforce_weekday', 'reinforce_hour', - 'reinforce_weekday', 'unanchors_at', + 'created_at', + 'updated_at', ]; public function services() { diff --git a/database/migrations/2021_05_14_210531_create_jump_bridge_fuel_levels_tables.php b/database/migrations/2021_05_14_210531_create_jump_bridge_fuel_levels_tables.php index c203fedd9..bf4ba8dbd 100644 --- a/database/migrations/2021_05_14_210531_create_jump_bridge_fuel_levels_tables.php +++ b/database/migrations/2021_05_14_210531_create_jump_bridge_fuel_levels_tables.php @@ -29,20 +29,27 @@ class CreateJumpBridgeFuelLevelsTables extends Migration $table->unsignedBigInteger('corporation_id'); $table->boolean('services'); $table->enum('state'. [ - + 'anchor_vulnerable', + 'anchoring', + 'armor_reinforce', + 'armor_vulnerable', + 'deploy_vulnerable', + 'fitting_invulnerable', + 'hull_reinforce', + 'hull_vulnerable', + 'online_deprecated', + 'onlining_vulnerable', + 'shield_vulnerable', + 'unanchored', + 'unknown', ]); $table->dateTime('state_timer_start')->nullable(); $table->dateTime('state_timer_end')->nullable(); $table->dateTime('fuel_expires')->nullable(); $table->unsignedBigInteger('profile_id'); - $table->unsignedBigInteger('position_x'); - $table->unsignedBigInteger('position_y'); - $table->unsignedBigInteger('position_z'); $table->dateTime('next_reinforce_apply')->nullable(); $table->unsignedInteger('next_reinforce_hour')->nullable(); - $table->unsignedInteger('next_reinforce_weekday')->nullable(); $table->unsignedInteger('reinforce_hour'); - $table->unsignedInteger('reinforce_weekday')->nullable(); $table->dateTime('unanchors_at')->nullable(); $table->timestamps(); }); @@ -54,7 +61,9 @@ class CreateJumpBridgeFuelLevelsTables extends Migration $table->unsignedBigInteger('structure_id'); $table->string('name'); $table->enum('state', [ - + 'online', + 'offline', + 'cleanup', ]); $table->timestamps(); }); @@ -67,11 +76,130 @@ class CreateJumpBridgeFuelLevelsTables extends Migration $table->boolean('is_singleton'); $table->unsignedBigInteger('item_id'); $table->enum('location_flag', [ - + 'AssetSafety', + 'AutoFit', + 'Bonus', + 'Booster', + 'BoosterBay', + 'Capsule', + 'Cargo', + 'CorpDeliveries', + 'CorpSAG1', + 'CorpSAG2', + 'CorpSAG3', + 'CorpSAG4', + 'CorpSAG5', + 'CorpSAG6', + 'CorpSAG7', + 'CrateLoot', + 'Deliveries', + 'DroneBay', + 'DustBattle', + 'DustDatabank', + 'FighterBay', + 'FighterTube0', + 'FighterTube1', + 'FighterTube2', + 'FighterTube3', + 'FighterTube4', + 'FleetHangar', + 'FrigateEscapeBay', + 'Hangar', + 'HangarAll', + 'HiSlot0', + 'HiSlot1', + 'HiSlot2', + 'HiSlot3', + 'HiSlot4', + 'HiSlot5', + 'HiSlot6', + 'HiSlot7', + 'HiddenModifiers', + 'Implant', + 'Impounded', + 'JunkyardReprocessed', + 'JunkyardTrashed', + 'LoSlot0', + 'LoSlot1', + 'LoSlot2', + 'LoSlot3', + 'LoSlot4', + 'LoSlot5', + 'LoSlot6', + 'LoSlot7', + 'Locked', + 'MedSlot0', + 'MedSlot1', + 'MedSlot2', + 'MedSlot3', + 'MedSlot4', + 'MedSlot5', + 'MedSlot6', + 'MedSlot7', + 'OfficeFolder', + 'Pilot', + 'PlanetSurface', + 'QuafeBay', + 'QuantumCoreRoom', + 'Reward', + 'RigSlot0', + 'RigSlot1', + 'RigSlot2', + 'RigSlot3', + 'RigSlot4', + 'RigSlot5', + 'RigSlot6', + 'RigSlot7', + 'SecondaryStorage', + 'ServiceSlot0', + 'ServiceSlot1', + 'ServiceSlot2', + 'ServiceSlot3', + 'ServiceSlot4', + 'ServiceSlot5', + 'ServiceSlot6', + 'ServiceSlot7', + 'ShipHangar', + 'ShipOffline', + 'Skill', + 'SkillInTraining', + 'SpecializedAmmoHold', + 'SpecializedCommandCenterHold', + 'SpecializedFuelBay', + 'SpecializedGasHold', + 'SpecializedIndustrialShipHold', + 'SpecializedLargeShipHold', + 'SpecializedMaterialBay', + 'SpecializedMediumShipHold', + 'SpecializedMineralHold', + 'SpecializedOreHold', + 'SpecializedPlanetaryCommoditiesHold', + 'SpecializedSalvageHold', + 'SpecializedShipHold', + 'SpecializedSmallShipHold', + 'StructureActive', + 'StructureFuel', + 'StructureInactive', + 'StructureOffline', + 'SubSystemBay', + 'SubSystemSlot0', + 'SubSystemSlot1', + 'SubSystemSlot2', + 'SubSystemSlot3', + 'SubSystemSlot4', + 'SubSystemSlot5', + 'SubSystemSlot6', + 'SubSystemSlot7', + 'Unlocked', + 'Wallet', + 'Wardrobe', ]); $table->unsignedBigInteger('location_id'); $table->enum('location_type', [ - + 'station', + 'solar_system', + 'item', + 'other', ]); $table->unsignedBigInteger('quantity'); $table->unsignedBigInteger('type_id');