Issue/329 refactor seeding (#337)
* Fix Contracts class names * Refactoring of the file seeds so it's not a mess * StyleCI fixes
This commit is contained in:
@@ -21,6 +21,14 @@ class DevInstall extends Command
|
||||
'acars.yml',
|
||||
];
|
||||
|
||||
private $databaseSeeder;
|
||||
|
||||
public function __construct(\DatabaseSeeder $databaseSeeder)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->databaseSeeder = $databaseSeeder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run dev related commands
|
||||
*
|
||||
@@ -45,16 +53,6 @@ class DevInstall extends Command
|
||||
'--seed' => true,
|
||||
]);
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
$this->info('Importing sample data');
|
||||
foreach ($this->yaml_imports as $yml) {
|
||||
$this->call('phpvms:yaml-import', [
|
||||
'files' => ['app/Database/seeds/'.$yml],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->info('Done!');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<?php
|
||||
|
||||
use App\Contracts\Migration;
|
||||
use App\Services\Installer\MigrationService;
|
||||
use App\Services\Installer\SeederService;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateSettingsTable extends Migration
|
||||
{
|
||||
private $migrationSvc;
|
||||
private $seederSvc;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->migrationSvc = new MigrationService();
|
||||
$this->seederSvc = app(SeederService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +38,7 @@ class CreateSettingsTable extends Migration
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$this->migrationSvc->syncAllSettings();
|
||||
$this->seederSvc->syncAllSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,21 +28,6 @@ class CreateRanksTable extends Migration
|
||||
|
||||
$table->unique('name');
|
||||
});
|
||||
|
||||
/**
|
||||
* Initial required data...
|
||||
*/
|
||||
$ranks = [
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'New Pilot',
|
||||
'hours' => 0,
|
||||
'acars_base_pay_rate' => 50,
|
||||
'manual_base_pay_rate' => 25,
|
||||
],
|
||||
];
|
||||
|
||||
$this->addData('ranks', $ranks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
<?php
|
||||
|
||||
use App\Services\DatabaseService;
|
||||
use App\Services\Installer\SeederService;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
private static $seed_mapper = [
|
||||
'local' => 'dev',
|
||||
'qa' => 'dev',
|
||||
'staging' => 'dev',
|
||||
];
|
||||
private $seederService;
|
||||
|
||||
private static $always_seed = [
|
||||
'permissions',
|
||||
];
|
||||
public function __construct()
|
||||
{
|
||||
$this->seederService = app(SeederService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
@@ -22,28 +19,6 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$env = App::environment();
|
||||
if (array_key_exists($env, self::$seed_mapper)) {
|
||||
$env = self::$seed_mapper[$env];
|
||||
}
|
||||
|
||||
Log::info('Seeding from environment '.$env);
|
||||
$path = database_path('seeds/'.$env.'.yml');
|
||||
|
||||
if (!file_exists($path)) {
|
||||
$path = database_path('seeds/prod.yml');
|
||||
}
|
||||
|
||||
$svc = app(DatabaseService::class);
|
||||
$svc->seed_from_yaml_file($path);
|
||||
|
||||
// Always seed/sync these
|
||||
foreach (self::$always_seed as $file) {
|
||||
Log::info('Importing '.$file);
|
||||
$path = database_path('seeds/'.$file.'.yml');
|
||||
if (file_exists($path)) {
|
||||
$svc->seed_from_yaml_file($path);
|
||||
}
|
||||
}
|
||||
$this->seederService->syncAllSeeds();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
acars:
|
||||
- id: acars_1
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: TNV
|
||||
lat: 30.28852
|
||||
lon: -96.058239
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_2
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: IAH
|
||||
lat: 29.95691
|
||||
lon: -95.345719
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_3
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: LCH
|
||||
lat: 30.14151
|
||||
lon: -93.105569
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_4
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: MEI
|
||||
lat: 32.37843
|
||||
lon: -88.804267
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_5
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: ATL
|
||||
lat: 33.62907
|
||||
lon: -84.435064
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_6
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: SIE
|
||||
lat: 39.0955
|
||||
lon: -74.800344
|
||||
created_at: now
|
||||
updated_at: now
|
||||
57
app/Database/seeds/dev/acars.yml
Normal file
57
app/Database/seeds/dev/acars.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
acars:
|
||||
id_column: id
|
||||
data:
|
||||
- id: acars_1
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: TNV
|
||||
lat: 30.28852
|
||||
lon: -96.058239
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_2
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: IAH
|
||||
lat: 29.95691
|
||||
lon: -95.345719
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_3
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: LCH
|
||||
lat: 30.14151
|
||||
lon: -93.105569
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_4
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: MEI
|
||||
lat: 32.37843
|
||||
lon: -88.804267
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_5
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: ATL
|
||||
lat: 33.62907
|
||||
lon: -84.435064
|
||||
created_at: now
|
||||
updated_at: now
|
||||
- id: acars_6
|
||||
pirep_id: pirepid_1
|
||||
type: 1
|
||||
nav_type: 2
|
||||
name: SIE
|
||||
lat: 39.0955
|
||||
lon: -74.800344
|
||||
created_at: now
|
||||
updated_at: now
|
||||
26
app/Database/seeds/dev/ranks.yml
Normal file
26
app/Database/seeds/dev/ranks.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
ranks:
|
||||
- id: 1
|
||||
name: New Pilot
|
||||
hours: 0
|
||||
acars_base_pay_rate: 50
|
||||
manual_base_pay_rate: 25
|
||||
- id: 2
|
||||
name: Junior First Officer
|
||||
hours: 10
|
||||
acars_base_pay_rate: 100
|
||||
manual_base_pay_rate: 90
|
||||
- id: 3
|
||||
name: First Officer
|
||||
hours: 15
|
||||
acars_base_pay_rate: 250
|
||||
manual_base_pay_rate: 200
|
||||
auto_approve_acars: 1
|
||||
auto_approve_manual: 1
|
||||
- id: 4
|
||||
name: Senior Captain
|
||||
hours: 20
|
||||
acars_base_pay_rate: 500
|
||||
manual_base_pay_rate: 400
|
||||
auto_approve_acars: 1
|
||||
auto_approve_manual: 1
|
||||
auto_promote: 0
|
||||
@@ -1,17 +1,19 @@
|
||||
|
||||
airlines:
|
||||
- id: 1
|
||||
icao: VMS
|
||||
iata: VM
|
||||
name: phpvms airlines
|
||||
country: us
|
||||
active: 1
|
||||
created_at: now
|
||||
updated_at: now
|
||||
#airlines:
|
||||
# - id: 1
|
||||
# icao: VMS
|
||||
# iata: VM
|
||||
# name: phpvms airlines
|
||||
# country: us
|
||||
# active: 1
|
||||
# created_at: now
|
||||
# updated_at: now
|
||||
|
||||
roles:
|
||||
- name: fleet-only
|
||||
display_name: Edit Fleet
|
||||
id_column: name
|
||||
data:
|
||||
- name: fleet-only
|
||||
display_name: Edit Fleet
|
||||
|
||||
users:
|
||||
- id: 1
|
||||
@@ -83,29 +85,6 @@ role_user:
|
||||
role_id: 2
|
||||
user_type: App\Models\User
|
||||
|
||||
# ranks
|
||||
ranks:
|
||||
- id: 2
|
||||
name: Junior First Officer
|
||||
hours: 10
|
||||
acars_base_pay_rate: 100
|
||||
manual_base_pay_rate: 90
|
||||
- id: 3
|
||||
name: First Officer
|
||||
hours: 15
|
||||
acars_base_pay_rate: 250
|
||||
manual_base_pay_rate: 200
|
||||
auto_approve_acars: 1
|
||||
auto_approve_manual: 1
|
||||
- id: 4
|
||||
name: Senior Captain
|
||||
hours: 20
|
||||
acars_base_pay_rate: 500
|
||||
manual_base_pay_rate: 400
|
||||
auto_approve_acars: 1
|
||||
auto_approve_manual: 1
|
||||
auto_promote: 0
|
||||
|
||||
awards:
|
||||
- id: 1
|
||||
name: Pilot 50 flights
|
||||
@@ -1,42 +1,41 @@
|
||||
# All of the different permissions that can be assigned to roles
|
||||
---
|
||||
permissions:
|
||||
- name: admin-access
|
||||
display_name: Admin Access
|
||||
description: Access the admin panel
|
||||
- name: airlines
|
||||
display_name: Airlines
|
||||
description: Create/edit airlines
|
||||
- name: airports
|
||||
display_name: Airports
|
||||
description: Create/edit airports
|
||||
- name: addons
|
||||
display_name: Addons
|
||||
description: Edit/view addons
|
||||
- name: awards
|
||||
display_name: Awards
|
||||
description: Create/edit award classes
|
||||
- name: flights
|
||||
display_name: Flights
|
||||
description: Create/edit flights
|
||||
- name: fleet
|
||||
display_name: Fleet
|
||||
description: Create/edit subfleets and fleets
|
||||
- name: fares
|
||||
display_name: Fares
|
||||
description: Create/edit fares
|
||||
- name: finances
|
||||
display_name: Finances
|
||||
description: Create/view finance related items
|
||||
- name: pireps
|
||||
display_name: PIREPs
|
||||
description: Accept/reject/edit PIREPs
|
||||
- name: ranks
|
||||
display_name: Ranks
|
||||
description: Create/edit ranks
|
||||
- name: users
|
||||
display_name: Users
|
||||
description: Create/edit users
|
||||
- name: settings
|
||||
display_name: Settings
|
||||
description: Edit VA settings
|
||||
- name: admin-access
|
||||
display_name: Admin Access
|
||||
description: Access the admin panel
|
||||
- name: airlines
|
||||
display_name: Airlines
|
||||
description: Create/edit airlines
|
||||
- name: airports
|
||||
display_name: Airports
|
||||
description: Create/edit airports
|
||||
- name: addons
|
||||
display_name: Addons
|
||||
description: Edit/view addons
|
||||
- name: awards
|
||||
display_name: Awards
|
||||
description: Create/edit award classes
|
||||
- name: flights
|
||||
display_name: Flights
|
||||
description: Create/edit flights
|
||||
- name: fleet
|
||||
display_name: Fleet
|
||||
description: Create/edit subfleets and fleets
|
||||
- name: fares
|
||||
display_name: Fares
|
||||
description: Create/edit fares
|
||||
- name: finances
|
||||
display_name: Finances
|
||||
description: Create/view finance related items
|
||||
- name: pireps
|
||||
display_name: PIREPs
|
||||
description: Accept/reject/edit PIREPs
|
||||
- name: ranks
|
||||
display_name: Ranks
|
||||
description: Create/edit ranks
|
||||
- name: users
|
||||
display_name: Users
|
||||
description: Create/edit users
|
||||
- name: settings
|
||||
display_name: Settings
|
||||
description: Edit VA settings
|
||||
|
||||
6
app/Database/seeds/prod/ranks.yml
Normal file
6
app/Database/seeds/prod/ranks.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
ranks:
|
||||
- id: 1
|
||||
name: New Pilot
|
||||
hours: 0
|
||||
acars_base_pay_rate: 50
|
||||
manual_base_pay_rate: 25
|
||||
@@ -13,6 +13,7 @@ use App\Models\Traits\FilesTrait;
|
||||
* @property string iata
|
||||
* @property string icao
|
||||
* @property string name
|
||||
* @property string full_name
|
||||
* @property string location
|
||||
* @property string country
|
||||
* @property string timezone
|
||||
|
||||
@@ -13,7 +13,7 @@ use Illuminate\Support\Collection;
|
||||
* @property string id
|
||||
* @property mixed ident
|
||||
* @property Airline airline
|
||||
* @property int airline_id
|
||||
* @property int airline_id
|
||||
* @property mixed flight_number
|
||||
* @property mixed route_code
|
||||
* @property int route_leg
|
||||
@@ -21,8 +21,9 @@ use Illuminate\Support\Collection;
|
||||
* @property Collection field_values
|
||||
* @property Collection fares
|
||||
* @property Collection subfleets
|
||||
* @property int days
|
||||
* @property Airport dep_airport
|
||||
* @property int days
|
||||
* @property string route
|
||||
* @property Airport dpt_airport
|
||||
* @property Airport arr_airport
|
||||
* @property Airport alt_airport
|
||||
* @property string dpt_airport_id
|
||||
|
||||
@@ -12,9 +12,9 @@ use App\Repositories\AcarsRepository;
|
||||
use App\Repositories\NavdataRepository;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Geotools\Coordinate\Coordinate;
|
||||
use League\Geotools\Geotools;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class GeoService
|
||||
@@ -109,6 +109,7 @@ class GeoService extends Service
|
||||
continue;
|
||||
}
|
||||
|
||||
$point = null;
|
||||
$size = \count($points);
|
||||
|
||||
if ($size === 0) {
|
||||
@@ -150,6 +151,10 @@ class GeoService extends Service
|
||||
}
|
||||
}
|
||||
|
||||
if ($point === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$coords[] = $point;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
namespace App\Services\Installer;
|
||||
|
||||
use App\Contracts\Service;
|
||||
use App\Models\Setting;
|
||||
use DB;
|
||||
use Log;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Nwidart\Modules\Facades\Module;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class MigrationService extends Service
|
||||
{
|
||||
private $counters = [];
|
||||
private $offsets = [];
|
||||
|
||||
protected function getMigrator()
|
||||
{
|
||||
$m = app('migrator');
|
||||
@@ -21,144 +17,6 @@ class MigrationService extends Service
|
||||
return $m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncronize all of the seed files, run this after the migrations
|
||||
* and on first install.
|
||||
*/
|
||||
public function syncAllSeeds(): void
|
||||
{
|
||||
$this->syncAllSettings();
|
||||
$this->syncAllPermissions();
|
||||
}
|
||||
|
||||
public function syncAllSettings(): void
|
||||
{
|
||||
$data = file_get_contents(database_path('/seeds/settings.yml'));
|
||||
$yml = Yaml::parse($data);
|
||||
foreach ($yml as $setting) {
|
||||
if (\trim($setting['key']) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addSetting($setting['key'], $setting);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncAllPermissions(): void
|
||||
{
|
||||
$data = file_get_contents(database_path('/seeds/permissions.yml'));
|
||||
$yml = Yaml::parse($data);
|
||||
foreach ($yml as $perm) {
|
||||
$count = DB::table('permissions')->where('name', $perm['name'])->count('name');
|
||||
if ($count === 0) {
|
||||
DB::table('permissions')->insert($perm);
|
||||
} else {
|
||||
DB::table('settings')
|
||||
->where('name', $perm['name'])
|
||||
->update($perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param $attrs
|
||||
*/
|
||||
public function addSetting($key, $attrs): void
|
||||
{
|
||||
$id = Setting::formatKey($key);
|
||||
$group = $attrs['group'];
|
||||
$order = $this->getNextOrderNumber($group);
|
||||
|
||||
$attrs = array_merge(
|
||||
[
|
||||
'id' => $id,
|
||||
'key' => $key,
|
||||
'offset' => $this->offsets[$group],
|
||||
'order' => $order,
|
||||
'name' => '',
|
||||
'group' => $group,
|
||||
'value' => '',
|
||||
'default' => $attrs['value'],
|
||||
'options' => '',
|
||||
'type' => 'hidden',
|
||||
'description' => '',
|
||||
],
|
||||
$attrs
|
||||
);
|
||||
|
||||
$count = DB::table('settings')->where('id', $id)->count('id');
|
||||
if ($count === 0) {
|
||||
DB::table('settings')->insert($attrs);
|
||||
} else {
|
||||
unset($attrs['value']); // Don't overwrite this
|
||||
DB::table('settings')
|
||||
->where('id', $id)
|
||||
->update($attrs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically figure out the offset and the start number for a group.
|
||||
* This way we don't need to mess with how to order things
|
||||
* When calling getNextOrderNumber(users) 31, will be returned, then 32, and so on
|
||||
*
|
||||
* @param $name
|
||||
* @param null $offset
|
||||
* @param int $start_offset
|
||||
*/
|
||||
private function addCounterGroup($name, $offset = null, $start_offset = 0): void
|
||||
{
|
||||
if ($offset === null) {
|
||||
$group = DB::table('settings')
|
||||
->where('group', $name)
|
||||
->first();
|
||||
|
||||
if ($group === null) {
|
||||
$offset = (int) DB::table('settings')->max('offset');
|
||||
if ($offset === null) {
|
||||
$offset = 0;
|
||||
$start_offset = 1;
|
||||
} else {
|
||||
$offset += 100;
|
||||
$start_offset = $offset + 1;
|
||||
}
|
||||
} else {
|
||||
// Now find the number to start from
|
||||
$start_offset = (int) DB::table('settings')->where('group', $name)->max('order');
|
||||
if ($start_offset === null) {
|
||||
$start_offset = $offset + 1;
|
||||
} else {
|
||||
$start_offset++;
|
||||
}
|
||||
|
||||
$offset = $group->offset;
|
||||
}
|
||||
}
|
||||
|
||||
$this->counters[$name] = $start_offset;
|
||||
$this->offsets[$name] = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next increment number from a group
|
||||
*
|
||||
* @param $group
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getNextOrderNumber($group): int
|
||||
{
|
||||
if (!\in_array($group, $this->counters, true)) {
|
||||
$this->addCounterGroup($group);
|
||||
}
|
||||
|
||||
$idx = $this->counters[$group];
|
||||
$this->counters[$group]++;
|
||||
|
||||
return $idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the possible paths that migrations exist.
|
||||
* Include looking in all of the modules Database/migrations directories
|
||||
@@ -168,7 +26,7 @@ class MigrationService extends Service
|
||||
public function getMigrationPaths(): array
|
||||
{
|
||||
$paths = [
|
||||
'core' => \App::databasePath().'/migrations',
|
||||
'core' => App::databasePath().'/migrations',
|
||||
];
|
||||
|
||||
$modules = Module::allEnabled();
|
||||
@@ -208,8 +66,8 @@ class MigrationService extends Service
|
||||
{
|
||||
$output = '';
|
||||
|
||||
\Artisan::call('migrate');
|
||||
$output .= trim(\Artisan::output());
|
||||
Artisan::call('migrate');
|
||||
$output .= trim(Artisan::output());
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
197
app/Services/Installer/SeederService.php
Normal file
197
app/Services/Installer/SeederService.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Installer;
|
||||
|
||||
use App\Contracts\Service;
|
||||
use App\Models\Setting;
|
||||
use App\Services\DatabaseService;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class SeederService extends Service
|
||||
{
|
||||
private $databaseSvc;
|
||||
|
||||
private $counters = [];
|
||||
private $offsets = [];
|
||||
|
||||
private static $seed_mapper = [
|
||||
'local' => 'dev',
|
||||
'qa' => 'dev',
|
||||
'staging' => 'dev',
|
||||
];
|
||||
|
||||
public function __construct(DatabaseService $databaseSvc)
|
||||
{
|
||||
$this->databaseSvc = $databaseSvc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncronize all of the seed files, run this after the migrations
|
||||
* and on first install.
|
||||
*/
|
||||
public function syncAllSeeds(): void
|
||||
{
|
||||
$this->syncAllYamlFileSeeds();
|
||||
$this->syncAllSettings();
|
||||
$this->syncAllPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all of the YAML files from disk and seed them
|
||||
*/
|
||||
public function syncAllYamlFileSeeds(): void
|
||||
{
|
||||
Log::info('Running seeder');
|
||||
$env = App::environment();
|
||||
if (array_key_exists($env, self::$seed_mapper)) {
|
||||
$env = self::$seed_mapper[$env];
|
||||
}
|
||||
|
||||
// Gather all of the files to seed
|
||||
collect()
|
||||
->concat(Storage::disk('seeds')->files($env))
|
||||
->map(function ($file) {
|
||||
return database_path('seeds/'.$file);
|
||||
})
|
||||
->filter(function ($file) {
|
||||
$info = pathinfo($file);
|
||||
return $info['extension'] === 'yml';
|
||||
})
|
||||
->each(function ($file) {
|
||||
Log::info('Seeding .'.$file);
|
||||
$this->databaseSvc->seed_from_yaml_file($file);
|
||||
});
|
||||
}
|
||||
|
||||
public function syncAllSettings(): void
|
||||
{
|
||||
$data = file_get_contents(database_path('/seeds/settings.yml'));
|
||||
$yml = Yaml::parse($data);
|
||||
foreach ($yml as $setting) {
|
||||
if (\trim($setting['key']) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addSetting($setting['key'], $setting);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncAllPermissions(): void
|
||||
{
|
||||
$data = file_get_contents(database_path('/seeds/permissions.yml'));
|
||||
$yml = Yaml::parse($data);
|
||||
foreach ($yml as $perm) {
|
||||
$count = DB::table('permissions')->where('name', $perm['name'])->count('name');
|
||||
if ($count === 0) {
|
||||
DB::table('permissions')->insert($perm);
|
||||
} else {
|
||||
DB::table('permissions')
|
||||
->where('name', $perm['name'])
|
||||
->update($perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param $attrs
|
||||
*/
|
||||
public function addSetting($key, $attrs): void
|
||||
{
|
||||
$id = Setting::formatKey($key);
|
||||
$group = $attrs['group'];
|
||||
$order = $this->getNextOrderNumber($group);
|
||||
|
||||
$attrs = array_merge(
|
||||
[
|
||||
'id' => $id,
|
||||
'key' => $key,
|
||||
'offset' => $this->offsets[$group],
|
||||
'order' => $order,
|
||||
'name' => '',
|
||||
'group' => $group,
|
||||
'value' => '',
|
||||
'default' => $attrs['value'],
|
||||
'options' => '',
|
||||
'type' => 'hidden',
|
||||
'description' => '',
|
||||
],
|
||||
$attrs
|
||||
);
|
||||
|
||||
$count = DB::table('settings')->where('id', $id)->count('id');
|
||||
if ($count === 0) {
|
||||
DB::table('settings')->insert($attrs);
|
||||
} else {
|
||||
unset($attrs['value']); // Don't overwrite this
|
||||
DB::table('settings')
|
||||
->where('id', $id)
|
||||
->update($attrs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically figure out the offset and the start number for a group.
|
||||
* This way we don't need to mess with how to order things
|
||||
* When calling getNextOrderNumber(users) 31, will be returned, then 32, and so on
|
||||
*
|
||||
* @param $name
|
||||
* @param null $offset
|
||||
* @param int $start_offset
|
||||
*/
|
||||
private function addCounterGroup($name, $offset = null, $start_offset = 0): void
|
||||
{
|
||||
if ($offset === null) {
|
||||
$group = DB::table('settings')
|
||||
->where('group', $name)
|
||||
->first();
|
||||
|
||||
if ($group === null) {
|
||||
$offset = (int) DB::table('settings')->max('offset');
|
||||
if ($offset === null) {
|
||||
$offset = 0;
|
||||
$start_offset = 1;
|
||||
} else {
|
||||
$offset += 100;
|
||||
$start_offset = $offset + 1;
|
||||
}
|
||||
} else {
|
||||
// Now find the number to start from
|
||||
$start_offset = (int) DB::table('settings')->where('group', $name)->max('order');
|
||||
if ($start_offset === null) {
|
||||
$start_offset = $offset + 1;
|
||||
} else {
|
||||
$start_offset++;
|
||||
}
|
||||
|
||||
$offset = $group->offset;
|
||||
}
|
||||
}
|
||||
|
||||
$this->counters[$name] = $start_offset;
|
||||
$this->offsets[$name] = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next increment number from a group
|
||||
*
|
||||
* @param $group
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getNextOrderNumber($group): int
|
||||
{
|
||||
if (!\in_array($group, $this->counters, true)) {
|
||||
$this->addCounterGroup($group);
|
||||
}
|
||||
|
||||
$idx = $this->counters[$group];
|
||||
$this->counters[$group]++;
|
||||
|
||||
return $idx;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Events\PirepRejected;
|
||||
use App\Events\UserStateChanged;
|
||||
use App\Events\UserStatsChanged;
|
||||
use App\Models\Acars;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Bid;
|
||||
use App\Models\Enums\AcarsType;
|
||||
use App\Models\Enums\PirepSource;
|
||||
@@ -338,7 +339,7 @@ class PirepService extends Service
|
||||
Log::info('PIREP '.$pirep->id.' state change to ACCEPTED');
|
||||
|
||||
// Update the aircraft
|
||||
$pirep->aircraft->flight_time += $pirep->flight_time;
|
||||
$pirep->aircraft->flight_time = $pirep->aircraft->flight_time + $pirep->flight_time;
|
||||
$pirep->aircraft->airport_id = $pirep->arr_airport_id;
|
||||
$pirep->aircraft->landing_time = $pirep->updated_at;
|
||||
$pirep->aircraft->save();
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Support;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Log;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Database
|
||||
@@ -48,12 +48,28 @@ class Database
|
||||
return $imported;
|
||||
}
|
||||
|
||||
foreach ($yml as $table => $rows) {
|
||||
foreach ($yml as $table => $data) {
|
||||
$imported[$table] = 0;
|
||||
|
||||
$id_column = 'id';
|
||||
if (array_key_exists('id_column', $data)) {
|
||||
$id_column = $data['id_column'];
|
||||
}
|
||||
|
||||
$ignore_on_update = [];
|
||||
if (array_key_exists('ignore_on_update', $data)) {
|
||||
$ignore_on_update = $data['ignore_on_update'];
|
||||
}
|
||||
|
||||
if (array_key_exists('data', $data)) {
|
||||
$rows = $data['data'];
|
||||
} else {
|
||||
$rows = $data;
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
try {
|
||||
static::insert_row($table, $row);
|
||||
static::insert_row($table, $row, $id_column, $ignore_on_update);
|
||||
} catch (QueryException $e) {
|
||||
if ($ignore_errors) {
|
||||
continue;
|
||||
@@ -70,15 +86,21 @@ class Database
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param $row
|
||||
*
|
||||
* @throws \Exception
|
||||
* @param string $table
|
||||
* @param array $row
|
||||
* @param string $id_col The ID column to use for update/insert
|
||||
* @param array $ignore_on_updates
|
||||
* @param bool $ignore_errors
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function insert_row($table, $row)
|
||||
{
|
||||
public static function insert_row(
|
||||
$table,
|
||||
$row,
|
||||
$id_col = 'id',
|
||||
$ignore_on_updates = [],
|
||||
$ignore_errors = true
|
||||
) {
|
||||
// encrypt any password fields
|
||||
if (array_key_exists('password', $row)) {
|
||||
$row['password'] = bcrypt($row['password']);
|
||||
@@ -95,12 +117,30 @@ class Database
|
||||
}
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
if (array_key_exists($id_col, $row)) {
|
||||
$count = DB::table($table)->where($id_col, $row[$id_col])->count($id_col);
|
||||
}
|
||||
|
||||
try {
|
||||
DB::table($table)->insert($row);
|
||||
if ($count > 0) {
|
||||
foreach ($ignore_on_updates as $ignore_column) {
|
||||
if (array_key_exists($ignore_column, $row)) {
|
||||
unset($row[$ignore_column]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::table($table)
|
||||
->where($id_col, $row[$id_col])
|
||||
->update($row);
|
||||
} else {
|
||||
DB::table($table)->insert($row);
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
Log::error($e);
|
||||
|
||||
throw $e;
|
||||
if (!$ignore_errors) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $row;
|
||||
|
||||
@@ -13,6 +13,11 @@ return [
|
||||
'root' => storage_path('app'),
|
||||
],
|
||||
|
||||
'seeds' => [
|
||||
'driver' => 'local',
|
||||
'root' => database_path('seeds'),
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => public_path('uploads'),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Modules\Installer\Http\Controllers;
|
||||
|
||||
use App\Contracts\Controller;
|
||||
use App\Services\Installer\MigrationService;
|
||||
use App\Services\Installer\SeederService;
|
||||
use function count;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
@@ -16,15 +17,19 @@ use Log;
|
||||
class UpdaterController extends Controller
|
||||
{
|
||||
private $migrationSvc;
|
||||
private $seederSvc;
|
||||
|
||||
/**
|
||||
* UpdaterController constructor.
|
||||
* @param MigrationService $migrationSvc
|
||||
* @param SeederService $seederSvc
|
||||
*/
|
||||
public function __construct(
|
||||
MigrationService $migrationSvc
|
||||
MigrationService $migrationSvc,
|
||||
SeederService $seederSvc
|
||||
) {
|
||||
$this->migrationSvc = $migrationSvc;
|
||||
$this->seederSvc = $seederSvc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,12 +71,12 @@ class UpdaterController extends Controller
|
||||
|
||||
$migrations = $this->migrationSvc->migrationsAvailable();
|
||||
if(count($migrations) === 0) {
|
||||
$this->migrationSvc->syncAllSeeds();
|
||||
$this->seederSvc->syncAllSeeds();
|
||||
return view('installer::update/steps/step3-update-complete');
|
||||
}
|
||||
|
||||
$output = $this->migrationSvc->runAllMigrations();
|
||||
$this->migrationSvc->syncAllSeeds();
|
||||
$this->seederSvc->syncAllSeeds();
|
||||
|
||||
return view('installer::update/steps/step2-migrations-done', [
|
||||
'console_output' => $output,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
@section('content')
|
||||
<h2>phpvms updater</h2>
|
||||
<p>Updates have been found, click run to complete the update!.</p>
|
||||
<p>Click run to complete the update!.</p>
|
||||
{{ Form::open(['route' => 'update.run_migrations', 'method' => 'post']) }}
|
||||
<p style="text-align: right">
|
||||
{{ Form::submit('Run >>', ['class' => 'btn btn-success']) }}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</whitelist>
|
||||
</filter>
|
||||
<php>
|
||||
<env name="APP_ENV" value="test"/>
|
||||
<env name="APP_ENV" value="unittest"/>
|
||||
<env name="APP_KEY" value="base64:ve66Z5Kt/zTN3p++0zOPu854PHfZkwJE5VuoFAlzHtI="/>
|
||||
<env name="APP_DEBUG" value="true"/>
|
||||
<env name="APP_LOG_LEVEL" value="debug"/>
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
'url' => '$SITE_URL$',
|
||||
|
||||
# Don't forget to change these when live
|
||||
'env' => 'local',
|
||||
'env' => 'prod',
|
||||
'debug' => false,
|
||||
],
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $MODULE_NAMESPACE$\$STUDLY_NAME$\Http\Controllers\Admin;
|
||||
|
||||
use App\Interfaces\Controller;
|
||||
use App\Contracts\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $MODULE_NAMESPACE$\$STUDLY_NAME$\Http\Controllers\Api;
|
||||
|
||||
use App\Interfaces\Controller;
|
||||
use App\Contracts\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $CLASS_NAMESPACE$;
|
||||
|
||||
use App\Interfaces\Controller;
|
||||
use App\Contracts\Controller;
|
||||
|
||||
/**
|
||||
* Class $CLASS$
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $CLASS_NAMESPACE$;
|
||||
|
||||
use App\Interfaces\Controller;
|
||||
use App\Contracts\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use App\Interfaces\Listener;
|
||||
use App\Contracts\Listener;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use App\Interfaces\Listener;
|
||||
use App\Contracts\Listener;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use $EVENTNAME$;
|
||||
use App\Interfaces\Listener;
|
||||
use App\Contracts\Listener;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use $EVENTNAME$;
|
||||
use App\Interfaces\Listener;
|
||||
use App\Contracts\Listener;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Interfaces\Migration;
|
||||
use App\Contracts\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Interfaces\Migration;
|
||||
use App\Contracts\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Interfaces\Migration;
|
||||
use App\Contracts\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Interfaces\Migration;
|
||||
use App\Contracts\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Interfaces\Migration;
|
||||
use App\Contracts\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use App\Interfaces\Model;
|
||||
use App\Contracts\Model;
|
||||
|
||||
/**
|
||||
* Class $CLASS$
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace {{namespace}};
|
||||
|
||||
use App\Interfaces\Widget;
|
||||
use App\Contracts\Widget;
|
||||
|
||||
/**
|
||||
* Class {{class}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace {{namespace}};
|
||||
|
||||
use App\Interfaces\Widget;
|
||||
use App\Contracts\Widget;
|
||||
|
||||
/**
|
||||
* Class {{class}}
|
||||
|
||||
@@ -84,9 +84,7 @@ class ApiTest extends TestCase
|
||||
$airlines = factory(App\Models\Airline::class, $size)->create();
|
||||
|
||||
$res = $this->get('/api/airlines');
|
||||
$body = $res->json();
|
||||
|
||||
$this->assertCount($size, $body['data']);
|
||||
$this->assertTrue($res->isOk());
|
||||
|
||||
$airline = $airlines->random();
|
||||
$this->get('/api/airlines/'.$airline->id)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use App\Models\UserAward;
|
||||
use App\Services\AwardService;
|
||||
use App\Services\PirepService;
|
||||
|
||||
class AwardsTest extends TestCase
|
||||
{
|
||||
@@ -10,8 +12,10 @@ class AwardsTest extends TestCase
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->awardSvc = app(\App\Services\AwardService::class);
|
||||
$this->pirepSvc = app(\App\Services\PirepService::class);
|
||||
$this->addData('base');
|
||||
$this->addData('fleet');
|
||||
$this->awardSvc = app(AwardService::class);
|
||||
$this->pirepSvc = app(PirepService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ class FinanceTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
$this->addData('base');
|
||||
$this->addData('fleet');
|
||||
|
||||
$this->expenseRepo = app(ExpenseRepository::class);
|
||||
$this->fareSvc = app(FareService::class);
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Pirep;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SettingRepository;
|
||||
use App\Services\FlightService;
|
||||
use App\Services\PirepService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PIREPTest extends TestCase
|
||||
@@ -19,8 +20,9 @@ class PIREPTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
$this->addData('base');
|
||||
$this->addData('fleet');
|
||||
|
||||
$this->pirepSvc = app('App\Services\PirepService');
|
||||
$this->pirepSvc = app(PirepService::class);
|
||||
$this->settingsRepo = app(SettingRepository::class);
|
||||
}
|
||||
|
||||
@@ -195,7 +197,6 @@ class PIREPTest extends TestCase
|
||||
public function testPilotStatsIncr()
|
||||
{
|
||||
$user = factory(User::class)->create([
|
||||
'airline_id' => 1,
|
||||
'flights' => 0,
|
||||
'flight_time' => 0,
|
||||
'rank_id' => 1,
|
||||
@@ -203,8 +204,9 @@ class PIREPTest extends TestCase
|
||||
|
||||
// Submit two PIREPs
|
||||
$pireps = factory(Pirep::class, 2)->create([
|
||||
'airline_id' => 1,
|
||||
'user_id' => $user->id,
|
||||
'airline_id' => $user->airline_id,
|
||||
'aircraft_id' => 1,
|
||||
'user_id' => $user->id,
|
||||
// 360min == 6 hours, rank should bump up
|
||||
'flight_time' => 360,
|
||||
]);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#
|
||||
#airlines:
|
||||
# - id: 1
|
||||
# icao: VMS
|
||||
# iata: VM
|
||||
# name: phpvms airlines
|
||||
# active: 1
|
||||
# created_at: now
|
||||
# updated_at: now
|
||||
airlines:
|
||||
- id: 1
|
||||
icao: VMS
|
||||
iata: VM
|
||||
name: phpvms airlines
|
||||
active: 1
|
||||
created_at: now
|
||||
updated_at: now
|
||||
|
||||
users:
|
||||
- id: 1
|
||||
@@ -31,6 +31,12 @@ role_user:
|
||||
user_type: App\Models\User
|
||||
|
||||
ranks:
|
||||
- id: 1
|
||||
name: New Pilot
|
||||
hours: 0
|
||||
acars_base_pay_rate: 50
|
||||
manual_base_pay_rate: 25
|
||||
|
||||
- id: 2
|
||||
name: Junior First Officer
|
||||
hours: 10
|
||||
|
||||
60
tests/data/fleet.yml
Normal file
60
tests/data/fleet.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
subfleets:
|
||||
- id: 1
|
||||
airline_id: 1
|
||||
name: 747-43X RB211-524G
|
||||
type: 744-3X-RB211
|
||||
cost_block_hour: 1000
|
||||
ground_handling_multiplier: 200
|
||||
- id: 2
|
||||
airline_id: 1
|
||||
name: 777-222ER GE90-76B
|
||||
type: 772-22ER-GE90-76B
|
||||
cost_block_hour: 500
|
||||
ground_handling_multiplier: 150
|
||||
- id: 3
|
||||
airline_id: 1
|
||||
name: 777-367 ER GE90-115B
|
||||
type: 772-36ER-GE90-115B
|
||||
cost_block_hour: 100
|
||||
ground_handling_multiplier: 150
|
||||
|
||||
aircraft:
|
||||
- id: 1
|
||||
subfleet_id: 1
|
||||
airport_id: KJFK
|
||||
name: Boeing 747-438
|
||||
registration: 001Z
|
||||
status: A
|
||||
- id: 2
|
||||
subfleet_id: 2
|
||||
airport_id: LGRP
|
||||
name: Boeing 777-200
|
||||
registration: C202
|
||||
status: A
|
||||
- id: 3
|
||||
subfleet_id: 1
|
||||
airport_id: KAUS
|
||||
name: Boeing 747-412
|
||||
registration: S2333
|
||||
status: A
|
||||
- id: 4
|
||||
subfleet_id: 1
|
||||
airport_id: KAUS
|
||||
name: Boeing 747-436 RETIRED
|
||||
registration:
|
||||
status: R
|
||||
|
||||
subfleet_rank:
|
||||
- rank_id: 1
|
||||
subfleet_id: 1
|
||||
- rank_id: 1
|
||||
subfleet_id: 2
|
||||
- rank_id: 1
|
||||
subfleet_id: 3
|
||||
- rank_id: 2
|
||||
subfleet_id: 2
|
||||
- rank_id: 2
|
||||
subfleet_id: 3
|
||||
- rank_id: 3
|
||||
subfleet_id: 3
|
||||
Reference in New Issue
Block a user