diff --git a/app/Console/Services/Importer.php b/app/Console/Services/Importer.php
deleted file mode 100644
index 1d727bc24..000000000
--- a/app/Console/Services/Importer.php
+++ /dev/null
@@ -1,704 +0,0 @@
-log = new ConsoleOutput();
-
- // The db credentials
- $this->creds = array_merge([
- 'host' => '127.0.0.1',
- 'port' => 3306,
- 'name' => '',
- 'user' => '',
- 'pass' => '',
- 'table_prefix' => '',
- ], $db_creds);
- }
-
- /**
- * @return int|void
- */
- public function run()
- {
- $this->reconnect();
-
- // Import all the different parts
- $this->importRanks();
- $this->importAirlines();
- $this->importAircraft();
- $this->importAirports();
-
- $this->importUsers();
- $this->importFlights();
- $this->importPireps();
-
- // Finish up
- $this->findLastPireps();
- $this->recalculateRanks();
- }
-
- /**
- * Reconnect to the old phpVMS DB using PDO
- */
- protected function reconnect()
- {
- $dsn = 'mysql:'.implode(';', [
- 'host='.$this->creds['host'],
- 'port='.$this->creds['port'],
- 'dbname='.$this->creds['name'],
- ]);
-
- $this->info('Connection string: '.$dsn);
-
- try {
- $this->conn = new PDO($dsn, $this->creds['user'], $this->creds['pass']);
- $this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
- } catch (\PDOException $e) {
- $this->error($e);
- exit();
- }
- }
-
- /**
- * @param $message
- */
- protected function comment($message)
- {
- $this->log->writeln(''.$message.'');
- }
-
- /**
- * @param $message
- */
- protected function error($message)
- {
- $this->log->writeln(''.$message.'');
- }
-
- /**
- * @param string|array $message
- */
- protected function info($message)
- {
- if (\is_array($message)) {
- /* @noinspection ForgottenDebugOutputInspection */
- print_r($message);
- } else {
- $this->log->writeln(''.$message.'');
- }
- }
-
- /**
- * Return the table name with the prefix
- *
- * @param $table
- *
- * @return string
- */
- protected function tableName($table)
- {
- if ($this->creds['table_prefix'] !== false) {
- return $this->creds['table_prefix'].$table;
- }
-
- return $table;
- }
-
- /**
- * @param \Illuminate\Database\Eloquent\Model $model
- *
- * @return bool
- */
- protected function saveModel($model)
- {
- try {
- $model->save();
-
- return true;
- } catch (QueryException $e) {
- if ($e->getCode() !== '23000') {
- $this->error($e);
- }
-
- return false;
- }
- }
-
- /**
- * Create a new mapping between an old ID and the new one
- *
- * @param $entity
- * @param $old_id
- * @param $new_id
- */
- protected function addMapping($entity, $old_id, $new_id)
- {
- if (!array_key_exists($entity, $this->mappedEntities)) {
- $this->mappedEntities[$entity] = [];
- }
-
- $this->mappedEntities[$entity][$old_id] = $new_id;
- }
-
- /**
- * Return the ID for a mapping
- *
- * @param $entity
- * @param $old_id
- *
- * @return bool
- */
- protected function getMapping($entity, $old_id)
- {
- if (!array_key_exists($entity, $this->mappedEntities)) {
- return 0;
- }
-
- $entity = $this->mappedEntities[$entity];
- if (array_key_exists($old_id, $entity)) {
- return $entity[$old_id];
- }
-
- return 0;
- }
-
- /**
- * @param $date
- *
- * @return Carbon
- */
- protected function parseDate($date)
- {
- $carbon = Carbon::parse($date);
-
- return $carbon;
- }
-
- /**
- * Take a decimal duration and convert it to minutes
- *
- * @param $duration
- *
- * @return float|int
- */
- protected function convertDuration($duration)
- {
- if (strpos($duration, '.') !== false) {
- $delim = '.';
- } elseif (strpos($duration, ':')) {
- $delim = ':';
- } else {
- // no delimiter, assume it's just a straight hour
- return (int) $duration * 60;
- }
-
- $hm = explode($delim, $duration);
- $hours = (int) $hm[0] * 60;
- $mins = (int) $hm[1];
-
- return $hours + $mins;
- }
-
- /**
- * @param $table
- *
- * @return mixed
- */
- protected function getTotalRows($table)
- {
- $table = $this->tableName($table);
-
- $sql = 'SELECT COUNT(*) FROM '.$table;
- $rows = $this->conn->query($sql)->fetchColumn();
-
- $this->info('Found '.$rows.' rows in '.$table);
-
- return (int) $rows;
- }
-
- /**
- * Read all the rows in a table, but read them in a batched manner
- *
- * @param string $table The name of the table
- * @param null $read_rows Number of rows to read
- *
- * @return \Generator
- */
- protected function readRows($table, $read_rows = null)
- {
- // Set the table prefix if it has been entered
- $this->tableName($table);
-
- $offset = 0;
- if ($read_rows === null) {
- $read_rows = self::BATCH_READ_ROWS;
- }
-
- $total_rows = $this->getTotalRows($table);
-
- while ($offset < $total_rows) {
- $rows_to_read = $offset + $read_rows;
- if ($rows_to_read > $total_rows) {
- $rows_to_read = $total_rows;
- }
-
- $this->info('Reading '.$offset.' to '.$rows_to_read.' of '.$total_rows);
-
- $sql = 'SELECT * FROM '.$this->tableName($table)
- .' LIMIT '.self::BATCH_READ_ROWS.' OFFSET '.$offset;
-
- try {
- foreach ($this->conn->query($sql) as $row) {
- yield $row;
- }
- } catch (PDOException $e) {
- // Without incrementing the offset, it should re-run the same query
- $this->error($e);
-
- if (strpos($e->getMessage(), 'server has gone away') !== false) {
- $this->reconnect();
- continue;
- }
- }
-
- $offset += self::BATCH_READ_ROWS;
- }
- }
-
- /**
- * Return the subfleet
- *
- * @return mixed
- */
- protected function getSubfleet()
- {
- $airline = Airline::first();
- $subfleet = Subfleet::firstOrCreate(
- ['airline_id' => $airline->id, 'name' => self::SUBFLEET_NAME],
- ['type' => 'PHPVMS']
- );
-
- return $subfleet;
- }
-
- /**
- * All the individual importers, done on a per-table basis
- * Some tables get saved locally for tables that use FK refs
- */
-
- /**
- * Import all of the ranks
- */
- protected function importRanks()
- {
- $this->comment('--- RANK IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('ranks') as $row) {
- $rank = Rank::firstOrCreate(
- ['name' => $row->rank],
- ['image_url' => $row->rankimage, 'hours' => $row->minhours]
- );
-
- $this->addMapping('ranks', $row->rankid, $rank->id);
- $this->addMapping('ranks', $row->rank, $rank->id);
-
- if ($rank->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' ranks');
- }
-
- /**
- * Import all of the airlines. Save them all in the private var $airlines
- * They're used to lookup from other reference tables
- */
- protected function importAirlines()
- {
- $this->comment('--- AIRLINE IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('airlines') as $row) {
- $airline = Airline::firstOrCreate(
- ['icao' => $row->code],
- ['iata' => $row->code, 'name' => $row->name, 'active' => $row->enabled]
- );
-
- $this->addMapping('airlines', $row->id, $airline->id);
- $this->addMapping('airlines', $row->code, $airline->id);
-
- if ($airline->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' airlines');
- }
-
- /**
- * Imported the aircraft
- */
- protected function importAircraft()
- {
- $this->comment('--- AIRCRAFT IMPORT ---');
-
- $subfleet = $this->getSubfleet();
-
- $this->info('Subfleet ID is '.$subfleet->id);
-
- $count = 0;
- foreach ($this->readRows('aircraft') as $row) {
- $aircraft = Aircraft::firstOrCreate(
- ['name' => $row->fullname, 'registration' => $row->registration],
- ['icao' => $row->icao,
- 'subfleet_id' => $subfleet->id,
- 'active' => $row->enabled,
- ]
- );
-
- $this->addMapping('aircraft', $row->id, $aircraft->id);
-
- if ($aircraft->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' aircraft');
- }
-
- /**
- * Import all of the airports
- */
- protected function importAirports()
- {
- $this->comment('--- AIRPORT IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('airports') as $row) {
- $attrs = [
- 'id' => trim($row->icao),
- 'icao' => trim($row->icao),
- 'name' => $row->name,
- 'country' => $row->country,
- 'lat' => $row->lat,
- 'lon' => $row->lng,
- 'hub' => $row->hub,
- ];
-
- $airport = Airport::updateOrCreate(
- ['id' => $attrs['id']],
- $attrs
- );
-
- if ($airport->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' airports');
- }
-
- /**
- * Import the flights and schedules
- */
- protected function importFlights()
- {
- $this->comment('--- FLIGHT SCHEDULE IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('schedules') as $row) {
- $airline_id = $this->getMapping('airlines', $row->code);
-
- $flight_num = trim($row->flightnum);
-
- $attrs = [
- 'dpt_airport_id' => $row->depicao,
- 'arr_airport_id' => $row->arricao,
- 'route' => $row->route ?: '',
- 'distance' => round($row->distance ?: 0, 2),
- 'level' => $row->flightlevel ?: 0,
- 'dpt_time' => $row->deptime ?: '',
- 'arr_time' => $row->arrtime ?: '',
- 'flight_time' => $this->convertDuration($row->flighttime) ?: '',
- 'notes' => $row->notes ?: '',
- 'active' => $row->enabled ?: true,
- ];
-
- try {
- $flight = Flight::updateOrCreate(
- ['airline_id' => $airline_id, 'flight_number' => $flight_num],
- $attrs
- );
- } catch (\Exception $e) {
- //$this->error($e);
- }
-
- $this->addMapping('flights', $row->id, $flight->id);
-
- // TODO: deserialize route_details into ACARS table
-
- if ($flight->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' flights');
- }
-
- /**
- * Import all of the PIREPs
- */
- protected function importPireps()
- {
- $this->comment('--- PIREP IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('pireps') as $row) {
- $pirep_id = $row->pirepid;
- $user_id = $this->getMapping('users', $row->pilotid);
- $airline_id = $this->getMapping('airlines', $row->code);
- $aircraft_id = $this->getMapping('aircraft', $row->aircraft);
-
- $attrs = [
- //'id' => $pirep_id,
- 'user_id' => $user_id,
- 'airline_id' => $airline_id,
- 'aircraft_id' => $aircraft_id,
- 'flight_number' => $row->flightnum ?: '',
- 'dpt_airport_id' => $row->depicao,
- 'arr_airport_id' => $row->arricao,
- 'block_fuel' => $row->fuelused,
- 'route' => $row->route ?: '',
- 'source_name' => $row->source,
- 'created_at' => $this->parseDate($row->submitdate),
- 'updated_at' => $this->parseDate($row->modifieddate),
- ];
-
- // Set the distance
- $distance = round($row->distance ?: 0, 2);
- $attrs['distance'] = $distance;
- $attrs['planned_distance'] = $distance;
-
- // Set the flight time properly
- $duration = $this->convertDuration($row->flighttime_stamp);
- $attrs['flight_time'] = $duration;
- $attrs['planned_flight_time'] = $duration;
-
- // Set how it was filed
- if (strtoupper($row->source) === 'MANUAL') {
- $attrs['source'] = PirepSource::MANUAL;
- } else {
- $attrs['source'] = PirepSource::ACARS;
- }
-
- // Set the flight type
- $row->flighttype = strtoupper($row->flighttype);
- if ($row->flighttype === 'P') {
- $attrs['flight_type'] = FlightType::SCHED_PAX;
- } elseif ($row->flighttype === 'C') {
- $attrs['flight_type'] = FlightType::SCHED_CARGO;
- } else {
- $attrs['flight_type'] = FlightType::CHARTER_PAX_ONLY;
- }
-
- // Set the flight level of the PIREP is set
- if (property_exists($row, 'flightlevel')) {
- $attrs['level'] = $row->flightlevel;
- } else {
- $attrs['level'] = 0;
- }
-
- $pirep = Pirep::updateOrCreate(
- ['id' => $pirep_id],
- $attrs
- );
-
- $source = strtoupper($row->source);
- if ($source === 'SMARTCARS') {
- // TODO: Parse smartcars log into the acars table
- } elseif ($source === 'KACARS') {
- // TODO: Parse kACARS log into acars table
- } elseif ($source === 'XACARS') {
- // TODO: Parse XACARS log into acars table
- }
-
- // TODO: Add extra fields in as PIREP fields
- $this->addMapping('pireps', $row->pirepid, $pirep->id);
-
- if ($pirep->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' pireps');
- }
-
- protected function importUsers()
- {
- $this->comment('--- USER IMPORT ---');
-
- $count = 0;
- foreach ($this->readRows('pilots', 50) as $row) {
- // TODO: What to do about pilot ids
-
- $name = $row->firstname.' '.$row->lastname;
-
- $airline_id = $this->getMapping('airlines', $row->code);
- $rank_id = $this->getMapping('ranks', $row->rank);
- $state = $this->getUserState($row->retired);
-
- $new_password = Str::random(60);
-
- $attrs = [
- 'name' => $name,
- 'password' => Hash::make($new_password),
- 'api_key' => Utils::generateApiKey(),
- 'airline_id' => $airline_id,
- 'rank_id' => $rank_id,
- 'home_airport_id' => $row->hub,
- 'curr_airport_id' => $row->hub,
- 'flights' => (int) $row->totalflights,
- 'flight_time' => Utils::hoursToMinutes($row->totalhours),
- 'state' => $state,
- 'created_at' => $this->parseDate($row->joindate),
- ];
-
- $user = User::updateOrCreate(
- ['email' => $row->email],
- $attrs
- );
-
- $this->addMapping('users', $row->pilotid, $user->id);
-
- if ($user->wasRecentlyCreated) {
- $count++;
- }
- }
-
- $this->info('Imported '.$count.' users');
- }
-
- /**
- * Go through and set the last PIREP ID for the users
- */
- protected function findLastPireps()
- {
- }
-
- /**
- * Recalculate all of the user ranks
- */
- protected function recalculateRanks()
- {
- /*$this->comment('--- RECALCULATING RANKS ---');*/
- }
-
- /**
- * Get the user's new state from their original state
- *
- * @param $state
- *
- * @return int
- */
- protected function getUserState($state)
- {
- // TODO: This state might differ between simpilot and classic version
-
- $state = (int) $state;
-
- // Declare array of classic states
- $phpvms_classic_states = [
- 'ACTIVE' => 0,
- 'INACTIVE' => 1,
- 'BANNED' => 2,
- 'ON_LEAVE' => 3,
- ];
-
- // Decide which state they will be in accordance with v7
- if ($state === $phpvms_classic_states['ACTIVE']) {
- return UserState::ACTIVE;
- }
-
- if ($state === $phpvms_classic_states['INACTIVE']) {
- // TODO: Make an inactive state?
- return UserState::REJECTED;
- }
-
- if ($state === $phpvms_classic_states['BANNED']) {
- return UserState::SUSPENDED;
- }
-
- if ($state === $phpvms_classic_states['ON_LEAVE']) {
- return UserState::ON_LEAVE;
- }
-
- $this->error('Unknown status: '.$state);
- return UserState::ACTIVE;
- }
-}
diff --git a/app/Contracts/Model.php b/app/Contracts/Model.php
index e186e9363..e4111609f 100644
--- a/app/Contracts/Model.php
+++ b/app/Contracts/Model.php
@@ -9,6 +9,8 @@
* @property bool $skip_mutator
*
* @method static where(array $array)
+ * @method static updateOrCreate(array $array, array $attrs)
+ * @method static truncate()
*/
abstract class Model extends \Illuminate\Database\Eloquent\Model
{
diff --git a/app/Database/migrations/2019_07_16_141152_users_add_pilot_id.php b/app/Database/migrations/2019_07_16_141152_users_add_pilot_id.php
index 29ad73a47..caad035cf 100644
--- a/app/Database/migrations/2019_07_16_141152_users_add_pilot_id.php
+++ b/app/Database/migrations/2019_07_16_141152_users_add_pilot_id.php
@@ -28,28 +28,6 @@ public function up()
// Migrate the current pilot IDs
DB::update('UPDATE `users` SET `pilot_id`=`id`');
-
- // Drop the old ID column and add a new one
- /*Schema::table('users', function (Blueprint $table) {
- $table->dropPrimary('users_id_primary');
- $table->dropColumn('id');
- $table->string('id', Model::ID_MAX_LENGTH)->primary();
- });
-
- // Update the users to use the `pilot_id` (so we don't need to migrate data from other tables)
- $users = DB::table('users')->get(['id']);
- foreach ($users as $user) {
- $user->id = $user->pilot_id;
- $user->save();
- }*/
-
- // role_user
- // permission_user
- // sessions
- // pireps
- // bids
- // news
- // user_awards
}
/**
diff --git a/app/Database/seeds/permissions.yml b/app/Database/seeds/permissions.yml
index a1fd89f6e..222cc5189 100644
--- a/app/Database/seeds/permissions.yml
+++ b/app/Database/seeds/permissions.yml
@@ -1,7 +1,7 @@
# All of the different permissions that can be assigned to roles
---
- name: admin-access
- display_name: Admin Access
+ display_name: Administrator
description: Access the admin panel
- name: airlines
display_name: Airlines
diff --git a/app/Http/Controllers/Admin/RolesController.php b/app/Http/Controllers/Admin/RolesController.php
index 00362ba09..daca92458 100644
--- a/app/Http/Controllers/Admin/RolesController.php
+++ b/app/Http/Controllers/Admin/RolesController.php
@@ -7,29 +7,33 @@
use App\Http\Requests\UpdateRoleRequest;
use App\Repositories\PermissionsRepository;
use App\Repositories\RoleRepository;
-use Flash;
+use App\Services\RoleService;
use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use Laracasts\Flash\Flash;
use Prettus\Repository\Criteria\RequestCriteria;
-use Response;
-/**
- * Class AirlinesController
- */
class RolesController extends Controller
{
private $permsRepo;
private $rolesRepo;
+ private $roleSvc;
/**
* AirlinesController constructor.
*
* @param PermissionsRepository $permsRepo
* @param RoleRepository $rolesRepo
+ * @param $roleSvc
*/
- public function __construct(PermissionsRepository $permsRepo, RoleRepository $rolesRepo)
- {
+ public function __construct(
+ PermissionsRepository $permsRepo,
+ RoleRepository $rolesRepo,
+ RoleService $roleSvc
+ ) {
$this->permsRepo = $permsRepo;
$this->rolesRepo = $rolesRepo;
+ $this->roleSvc = $roleSvc;
}
/**
@@ -145,14 +149,8 @@ public function update($id, UpdateRoleRequest $request)
return redirect(route('admin.roles.index'));
}
- $this->rolesRepo->update($request->all(), $id);
-
- // Update the permissions, filter out null/invalid values
- $perms = collect($request->permissions)->filter(static function ($v, $k) {
- return $v;
- });
-
- $role->permissions()->sync($perms);
+ $this->roleSvc->updateRole($role, $request->all());
+ $this->roleSvc->setPermissionsForRole($role, $request->permissions);
Flash::success('Roles updated successfully.');
return redirect(route('admin.roles.index'));
diff --git a/app/Models/Permission.php b/app/Models/Permission.php
index beaa2e89c..94a893e96 100644
--- a/app/Models/Permission.php
+++ b/app/Models/Permission.php
@@ -4,6 +4,9 @@
use Laratrust\Models\LaratrustPermission;
+/**
+ * @method static firstOrCreate(array $array, array $array1)
+ */
class Permission extends LaratrustPermission
{
}
diff --git a/app/Models/Role.php b/app/Models/Role.php
index 176165d73..6ddf1cf08 100644
--- a/app/Models/Role.php
+++ b/app/Models/Role.php
@@ -6,6 +6,7 @@
/**
* @method static where(string $string, $group)
+ * @method static firstOrCreate(array $array, array $array1)
*/
class Role extends LaratrustRole
{
diff --git a/app/Models/User.php b/app/Models/User.php
index 69a1f67df..eae1e6b60 100755
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -34,6 +34,10 @@
* @property int state
* @property bool opt_in
* @property string last_pirep_id
+ *
+ * @method static updateOrCreate(array $array, array $attrs)
+ * @method static where()
+ * @method static truncate()
* @mixin \Illuminate\Notifications\Notifiable
* @mixin \Laratrust\Traits\LaratrustUserTrait
*/
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 08de6322b..9b3acf514 100755
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -26,8 +26,8 @@
use App\Repositories\SettingRepository;
use App\Services\ModuleService;
use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
-use View;
class AppServiceProvider extends ServiceProvider
{
diff --git a/app/Services/Installer/SeederService.php b/app/Services/Installer/SeederService.php
index 2ae2fed94..c0303cd8b 100644
--- a/app/Services/Installer/SeederService.php
+++ b/app/Services/Installer/SeederService.php
@@ -236,6 +236,13 @@ private function settingsSeedsPending(): bool
return true;
}
+ // See if any of these column values have changed
+ foreach (['name', 'description'] as $column) {
+ if ($row[$column] !== $setting[$column]) {
+ return true;
+ }
+ }
+
// See if any of the options have changed
if ($row->type === 'select') {
if ($row->options !== $setting['options']) {
@@ -259,10 +266,20 @@ private function permissionsSeedsPending(): bool
$yml = Yaml::parse($data);
foreach ($yml as $perm) {
- $count = DB::table('permissions')->where('name', $perm['name'])->count('name');
- if ($count === 0) {
+ $row = DB::table('permissions')
+ ->where('name', $perm['name'])
+ ->first();
+
+ if (!$row) {
return true;
}
+
+ // See if any of these column values have changed
+ foreach (['display_name', 'description'] as $column) {
+ if ($row[$column] !== $perm[$column]) {
+ return true;
+ }
+ }
}
return false;
diff --git a/app/Services/RoleService.php b/app/Services/RoleService.php
new file mode 100644
index 000000000..1b6c00588
--- /dev/null
+++ b/app/Services/RoleService.php
@@ -0,0 +1,47 @@
+roleRepo = $roleRepo;
+ }
+
+ /**
+ * Update a role with the given attributes
+ *
+ * @param Role $role
+ * @param array $attrs
+ *
+ * @return Role
+ */
+ public function updateRole(Role $role, array $attrs)
+ {
+ $role->update($attrs);
+ $role->save();
+
+ return $role;
+ }
+
+ /**
+ * @param Role $role
+ * @param array $permissions
+ */
+ public function setPermissionsForRole(Role $role, array $permissions)
+ {
+ // Update the permissions, filter out null/invalid values
+ $perms = collect($permissions)->filter(static function ($v, $k) {
+ return $v;
+ });
+
+ $role->permissions()->sync($perms);
+ }
+}
diff --git a/app/Services/UserService.php b/app/Services/UserService.php
index bc233a7ab..bc62636bb 100644
--- a/app/Services/UserService.php
+++ b/app/Services/UserService.php
@@ -48,14 +48,14 @@ public function __construct(
* Register a pilot. Also attaches the initial roles
* required, and then triggers the UserRegistered event
*
- * @param User $user User model
- * @param array $groups Additional groups to assign
+ * @param User $user User model
+ * @param array $roles List of "display_name" of groups to assign
*
* @throws \Exception
*
* @return mixed
*/
- public function createUser(User $user, array $groups = null)
+ public function createUser(User $user, array $roles = null)
{
// Determine if we want to auto accept
if (setting('pilots.auto_accept') === true) {
@@ -66,15 +66,10 @@ public function createUser(User $user, array $groups = null)
$user->save();
- // Attach the user roles
- // $role = Role::where('name', 'user')->first();
- // $user->attachRole($role);
-
// Attach any additional roles
- if (!empty($groups) && is_array($groups)) {
- foreach ($groups as $group) {
- $role = Role::where('name', $group)->first();
- $user->attachRole($role);
+ if (!empty($roles) && is_array($roles)) {
+ foreach ($roles as $role) {
+ $this->addUserToRole($user, $role);
}
}
@@ -87,6 +82,32 @@ public function createUser(User $user, array $groups = null)
return $user;
}
+ /**
+ * Add a user to a given role
+ *
+ * @param User $user
+ * @param string $roleName
+ *
+ * @return User
+ */
+ public function addUserToRole(User $user, $roleName): User
+ {
+ $role = Role::where('name', $roleName)->first();
+ $user->attachRole($role);
+
+ return $user;
+ }
+
+ /**
+ * Find and return the next available pilot ID (usually just the max+1)
+ *
+ * @return int
+ */
+ public function getNextAvailablePilotId(): int
+ {
+ return (int) User::max('pilot_id') + 1;
+ }
+
/**
* Find the next available pilot ID and set the current user's pilot_id to that +1
* Called from UserObserver right now after a record is created
@@ -101,8 +122,7 @@ public function findAndSetPilotId(User $user): User
return $user;
}
- $max = (int) User::max('pilot_id');
- $user->pilot_id = $max + 1;
+ $user->pilot_id = $this->getNextAvailablePilotId();
$user->save();
Log::info('Set pilot ID for user '.$user->id.' to '.$user->pilot_id);
diff --git a/intellij_style.xml b/intellij_style.xml
index 903cf02a8..9c0b52567 100644
--- a/intellij_style.xml
+++ b/intellij_style.xml
@@ -1,16 +1,15 @@
-
-
+
-
+
+
+
+
+
@@ -21,11 +20,17 @@
-
+
+
+
+
+
+
+
diff --git a/modules/.gitignore b/modules/.gitignore
index 4e7f20b3f..75e889fbc 100644
--- a/modules/.gitignore
+++ b/modules/.gitignore
@@ -4,6 +4,7 @@
/*/
!.gitignore
!/Awards
+!/Importer
!/Installer
!/Sample
!/Vacentral
diff --git a/app/Console/Commands/ImportFromClassic.php b/modules/Installer/Console/Commands/ImportFromClassicCommand.php
similarity index 74%
rename from app/Console/Commands/ImportFromClassic.php
rename to modules/Installer/Console/Commands/ImportFromClassicCommand.php
index fbad3f503..c853f12dd 100644
--- a/app/Console/Commands/ImportFromClassic.php
+++ b/modules/Installer/Console/Commands/ImportFromClassicCommand.php
@@ -1,10 +1,11 @@
$this->argument('table_prefix'),
];
- $importerSvc = new \App\Console\Services\Importer($db_creds);
- $importerSvc->run();
+ $importerSvc = new Importer();
+ $importerSvc->run($db_creds);
}
}
diff --git a/modules/Installer/Contracts/ImporterContract.php b/modules/Installer/Contracts/ImporterContract.php
new file mode 100644
index 000000000..bdbb49e55
--- /dev/null
+++ b/modules/Installer/Contracts/ImporterContract.php
@@ -0,0 +1,8 @@
+moduleSvc = app(ModuleService::class);
+ $this->registerCommands();
$this->registerRoutes();
$this->registerTranslations();
$this->registerConfig();
@@ -25,6 +27,13 @@ public function boot()
$this->loadMigrationsFrom(__DIR__.'/../Database/migrations');
}
+ protected function registerCommands()
+ {
+ $this->commands([
+ ImportFromClassicCommand::class,
+ ]);
+ }
+
/**
* Register the routes
*/
diff --git a/modules/Installer/Services/Importer/BaseImporter.php b/modules/Installer/Services/Importer/BaseImporter.php
new file mode 100644
index 000000000..33aa0516e
--- /dev/null
+++ b/modules/Installer/Services/Importer/BaseImporter.php
@@ -0,0 +1,60 @@
+db = $db;
+ $this->idMapper = $mapper;
+ }
+
+ /**
+ * @param $date
+ *
+ * @return Carbon
+ */
+ protected function parseDate($date)
+ {
+ $carbon = Carbon::parse($date);
+
+ return $carbon;
+ }
+
+ /**
+ * Take a decimal duration and convert it to minutes
+ *
+ * @param $duration
+ *
+ * @return float|int
+ */
+ protected function convertDuration($duration)
+ {
+ if (strpos($duration, '.') !== false) {
+ $delim = '.';
+ } elseif (strpos($duration, ':')) {
+ $delim = ':';
+ } else {
+ // no delimiter, assume it's just a straight hour
+ return (int) $duration * 60;
+ }
+
+ $hm = explode($delim, $duration);
+ $hours = (int) $hm[0] * 60;
+ $mins = (int) $hm[1];
+
+ return $hours + $mins;
+ }
+}
diff --git a/modules/Installer/Services/Importer/BaseStage.php b/modules/Installer/Services/Importer/BaseStage.php
new file mode 100644
index 000000000..e7b819ebd
--- /dev/null
+++ b/modules/Installer/Services/Importer/BaseStage.php
@@ -0,0 +1,34 @@
+db = $db;
+ $this->idMapper = $mapper;
+ }
+
+ /**
+ * Run all of the given importers
+ */
+ public function run()
+ {
+ foreach ($this->importers as $klass) {
+ $importer = new $klass($this->db, $this->idMapper);
+ $importer->run();
+ }
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importer.php b/modules/Installer/Services/Importer/Importer.php
new file mode 100644
index 000000000..bb5b71e28
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importer.php
@@ -0,0 +1,62 @@
+idMapper = app(IdMapper::class);
+ }
+
+ /**
+ * @param array $creds Database credentials
+ *
+ * @return int|void
+ */
+ public function run($creds)
+ {
+ // The db credentials
+ $this->db = new ImporterDB(array_merge([
+ 'host' => '127.0.0.1',
+ 'port' => 3306,
+ 'name' => '',
+ 'user' => '',
+ 'pass' => '',
+ 'table_prefix' => '',
+ ], $creds));
+
+ (new Stage1($this->db, $this->idMapper))->run();
+ (new Stage2($this->db, $this->idMapper))->run();
+ (new Stage3($this->db, $this->idMapper))->run();
+ (new Stage4($this->db, $this->idMapper))->run();
+ (new Stage5($this->db, $this->idMapper))->run();
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/AircraftImporter.php b/modules/Installer/Services/Importer/Importers/AircraftImporter.php
new file mode 100644
index 000000000..9dc7469f2
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/AircraftImporter.php
@@ -0,0 +1,63 @@
+comment('--- AIRCRAFT IMPORT ---');
+
+ $subfleet = $this->getSubfleet();
+
+ $this->info('Subfleet ID is '.$subfleet->id);
+
+ $count = 0;
+ foreach ($this->db->readRows('aircraft') as $row) {
+ $where = [
+ 'name' => $row->fullname,
+ 'registration' => $row->registration,
+ ];
+
+ $aircraft = Aircraft::firstOrCreate($where, [
+ 'icao' => $row->icao,
+ 'subfleet_id' => $subfleet->id,
+ 'active' => $row->enabled,
+ ]);
+
+ $this->idMapper->addMapping('aircraft', $row->id, $aircraft->id);
+
+ if ($aircraft->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' aircraft');
+ }
+
+ /**
+ * Return the subfleet
+ *
+ * @return mixed
+ */
+ protected function getSubfleet()
+ {
+ $airline = Airline::first();
+ $subfleet = Subfleet::firstOrCreate([
+ 'airline_id' => $airline->id,
+ 'name' => self::SUBFLEET_NAME,
+ ], ['type' => 'PHPVMS']);
+
+ return $subfleet;
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/AirlineImporter.php b/modules/Installer/Services/Importer/Importers/AirlineImporter.php
new file mode 100644
index 000000000..021f236f3
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/AirlineImporter.php
@@ -0,0 +1,32 @@
+comment('--- AIRLINE IMPORT ---');
+
+ $count = 0;
+ foreach ($this->db->readRows('airlines') as $row) {
+ $airline = Airline::firstOrCreate(['icao' => $row->code], [
+ 'iata' => $row->code,
+ 'name' => $row->name,
+ 'active' => $row->enabled,
+ ]);
+
+ $this->idMapper->addMapping('airlines', $row->id, $airline->id);
+ $this->idMapper->addMapping('airlines', $row->code, $airline->id);
+
+ if ($airline->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' airlines');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/AirportImporter.php b/modules/Installer/Services/Importer/Importers/AirportImporter.php
new file mode 100644
index 000000000..b311a7cef
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/AirportImporter.php
@@ -0,0 +1,35 @@
+comment('--- AIRPORT IMPORT ---');
+
+ $count = 0;
+ foreach ($this->db->readRows('airports') as $row) {
+ $attrs = [
+ 'id' => trim($row->icao),
+ 'icao' => trim($row->icao),
+ 'name' => $row->name,
+ 'country' => $row->country,
+ 'lat' => $row->lat,
+ 'lon' => $row->lng,
+ 'hub' => $row->hub,
+ ];
+
+ $airport = Airport::updateOrCreate(['id' => $attrs['id']], $attrs);
+
+ if ($airport->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' airports');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/FlightImporter.php b/modules/Installer/Services/Importer/Importers/FlightImporter.php
new file mode 100644
index 000000000..feb5bf121
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/FlightImporter.php
@@ -0,0 +1,51 @@
+comment('--- FLIGHT SCHEDULE IMPORT ---');
+
+ $count = 0;
+ foreach ($this->db->readRows('schedules') as $row) {
+ $airline_id = $this->idMapper->getMapping('airlines', $row->code);
+
+ $flight_num = trim($row->flightnum);
+
+ $attrs = [
+ 'dpt_airport_id' => $row->depicao,
+ 'arr_airport_id' => $row->arricao,
+ 'route' => $row->route ?: '',
+ 'distance' => round($row->distance ?: 0, 2),
+ 'level' => $row->flightlevel ?: 0,
+ 'dpt_time' => $row->deptime ?: '',
+ 'arr_time' => $row->arrtime ?: '',
+ 'flight_time' => $this->convertDuration($row->flighttime) ?: '',
+ 'notes' => $row->notes ?: '',
+ 'active' => $row->enabled ?: true,
+ ];
+
+ try {
+ $w = ['airline_id' => $airline_id, 'flight_number' => $flight_num];
+ $flight = Flight::updateOrCreate($w, $attrs);
+ } catch (\Exception $e) {
+ //$this->error($e);
+ }
+
+ $this->idMapper->addMapping('flights', $row->id, $flight->id);
+
+ // TODO: deserialize route_details into ACARS table
+
+ if ($flight->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' flights');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/GroupImporter.php b/modules/Installer/Services/Importer/Importers/GroupImporter.php
new file mode 100644
index 000000000..662736f61
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/GroupImporter.php
@@ -0,0 +1,95 @@
+ 0x1,
+ 'EDIT_PAGES' => 0x2,
+ 'EDIT_DOWNLOADS' => 0x4,
+ 'EMAIL_PILOTS' => 0x8,
+ 'EDIT_AIRLINES' => 0x10, //
+ 'EDIT_FLEET' => 0x20, //
+ 'EDIT_SCHEDULES' => 0x80, //
+ 'IMPORT_SCHEDULES' => 0x100, //
+ 'MODERATE_REGISTRATIONS' => 0x200,
+ 'EDIT_PILOTS' => 0x400, //
+ 'EDIT_GROUPS' => 0x800,
+ 'EDIT_RANKS' => 0x1000, //
+ 'EDIT_AWARDS' => 0x2000, //
+ 'MODERATE_PIREPS' => 0x4000, //
+ 'VIEW_FINANCES' => 0x8000, //
+ 'EDIT_EXPENSES' => 0x10000, //
+ 'EDIT_SETTINGS' => 0x20000, //
+ 'EDIT_PIREPS_FIELDS' => 0x40000, //
+ 'EDIT_PROFILE_FIELDS' => 0x80000, //
+ 'EDIT_VACENTRAL' => 0x100000,
+ 'ACCESS_ADMIN' => 0x2000000,
+ 'FULL_ADMIN' => 35651519, //
+ ];
+
+ /**
+ * Map a legacy value over to one of the current permission values
+ */
+ protected $legacy_to_permission = [
+ 'FULL_ADMIN' => 'admin',
+ 'EDIT_AIRLINES' => 'airlines',
+ 'EDIT_AWARDS' => 'awards',
+ 'EDIT_FLEET' => 'fleet',
+ 'EDIT_EXPENCES' => 'finances',
+ 'VIEW_FINANCES' => 'finances',
+ 'EDIT_SCHEDULES' => 'flights',
+ 'EDIT_PILOTS' => 'users',
+ 'EDIT_PROFILE_FIELDS' => 'users',
+ 'EDIT_SETTINGS' => 'settings',
+ 'MODERATE_PIREPS' => 'pireps',
+ 'EDIT_PIREPS_FIELDS' => 'pireps',
+ 'EDIT_RANKS' => 'ranks',
+ 'MODERATE_REGISTRATIONS' => 'users',
+ ];
+
+ public function run()
+ {
+ $this->comment('--- ROLES IMPORT ---');
+
+ $roleSvc = app(RoleService::class);
+
+ $count = 0;
+ foreach ($this->db->readRows('groups') as $row) {
+ $role = Role::firstOrCreate(['display_name' => $row->name], []);
+ $this->idMapper->addMapping('group', $row->groupid, $role->id);
+
+ // See if the permission set mask contains one of the mappings above
+ // Add all of the ones which apply, and then set them on the new role
+ $permissions = [];
+ foreach ($this->legacy_permission_set as $legacy_name => $mask) {
+ if (($row->permissions & $mask) === true) {
+ if (!array_key_exists($legacy_name, $this->legacy_to_permission)) {
+ continue;
+ }
+
+ $permissions[] = $this->legacy_to_permission[$legacy_name];
+ }
+ }
+
+ $roleSvc->setPermissionsForRole($role, $permissions);
+
+ if ($role->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' ranks');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/PirepImporter.php b/modules/Installer/Services/Importer/Importers/PirepImporter.php
new file mode 100644
index 000000000..87268b55c
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/PirepImporter.php
@@ -0,0 +1,92 @@
+comment('--- PIREP IMPORT ---');
+
+ $count = 0;
+ foreach ($this->db->readRows('pireps') as $row) {
+ $pirep_id = $row->pirepid;
+ $user_id = $this->idMapper->getMapping('users', $row->pilotid);
+ $airline_id = $this->idMapper->getMapping('airlines', $row->code);
+ $aircraft_id = $this->idMapper->getMapping('aircraft', $row->aircraft);
+
+ $attrs = [
+ 'user_id' => $user_id,
+ 'airline_id' => $airline_id,
+ 'aircraft_id' => $aircraft_id,
+ 'flight_number' => $row->flightnum ?: '',
+ 'dpt_airport_id' => $row->depicao,
+ 'arr_airport_id' => $row->arricao,
+ 'block_fuel' => $row->fuelused,
+ 'route' => $row->route ?: '',
+ 'source_name' => $row->source,
+ 'created_at' => $this->parseDate($row->submitdate),
+ 'updated_at' => $this->parseDate($row->modifieddate),
+ ];
+
+ // Set the distance
+ $distance = round($row->distance ?: 0, 2);
+ $attrs['distance'] = $distance;
+ $attrs['planned_distance'] = $distance;
+
+ // Set the flight time properly
+ $duration = $this->convertDuration($row->flighttime_stamp);
+ $attrs['flight_time'] = $duration;
+ $attrs['planned_flight_time'] = $duration;
+
+ // Set how it was filed
+ if (strtoupper($row->source) === 'MANUAL') {
+ $attrs['source'] = PirepSource::MANUAL;
+ } else {
+ $attrs['source'] = PirepSource::ACARS;
+ }
+
+ // Set the flight type
+ $row->flighttype = strtoupper($row->flighttype);
+ if ($row->flighttype === 'P') {
+ $attrs['flight_type'] = FlightType::SCHED_PAX;
+ } elseif ($row->flighttype === 'C') {
+ $attrs['flight_type'] = FlightType::SCHED_CARGO;
+ } else {
+ $attrs['flight_type'] = FlightType::CHARTER_PAX_ONLY;
+ }
+
+ // Set the flight level of the PIREP is set
+ if (property_exists($row, 'flightlevel')) {
+ $attrs['level'] = $row->flightlevel;
+ } else {
+ $attrs['level'] = 0;
+ }
+
+ $pirep = Pirep::updateOrCreate(['id' => $pirep_id], $attrs);
+
+ $source = strtoupper($row->source);
+ if ($source === 'SMARTCARS') {
+ // TODO: Parse smartcars log into the acars table
+ } elseif ($source === 'KACARS') {
+ // TODO: Parse kACARS log into acars table
+ } elseif ($source === 'XACARS') {
+ // TODO: Parse XACARS log into acars table
+ }
+
+ // TODO: Add extra fields in as PIREP fields
+ $this->idMapper->addMapping('pireps', $row->pirepid, $pirep->id);
+
+ if ($pirep->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' pireps');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/RankImport.php b/modules/Installer/Services/Importer/Importers/RankImport.php
new file mode 100644
index 000000000..d58f74e84
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/RankImport.php
@@ -0,0 +1,31 @@
+comment('--- RANK IMPORT ---');
+
+ $count = 0;
+ foreach ($this->db->readRows('ranks') as $row) {
+ $rank = Rank::firstOrCreate(['name' => $row->rank], [
+ 'image_url' => $row->rankimage,
+ 'hours' => $row->minhours,
+ ]);
+
+ $this->idMapper->addMapping('ranks', $row->rankid, $rank->id);
+ $this->idMapper->addMapping('ranks', $row->rank, $rank->id);
+
+ if ($rank->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' ranks');
+ }
+}
diff --git a/modules/Installer/Services/Importer/Importers/UserImport.php b/modules/Installer/Services/Importer/Importers/UserImport.php
new file mode 100644
index 000000000..0ab6740c7
--- /dev/null
+++ b/modules/Installer/Services/Importer/Importers/UserImport.php
@@ -0,0 +1,140 @@
+comment('--- USER IMPORT ---');
+
+ $this->userSvc = app(UserService::class);
+
+ $count = 0;
+ $first_row = true;
+ foreach ($this->db->readRows('pilots', 50) as $row) {
+ $pilot_id = $row->pilotid; // This isn't their actual ID
+ $name = $row->firstname.' '.$row->lastname;
+
+ // Figure out which airline, etc, they belong to
+ $airline_id = $this->idMapper->getMapping('airlines', $row->code);
+ $rank_id = $this->idMapper->getMapping('ranks', $row->rank);
+ $state = $this->getUserState($row->retired);
+
+ if ($first_row) {
+ $new_password = 'admin';
+ $first_row = false;
+ } else {
+ $new_password = Str::random(60);
+ }
+
+ // Look for a user with that pilot ID already. If someone has it
+ if ($this->userSvc->isPilotIdAlreadyUsed($pilot_id)) {
+ Log::info('User with pilot id '.$pilot_id.' exists, reassigning');
+ $pilot_id = $this->userSvc->getNextAvailablePilotId();
+ }
+
+ $attrs = [
+ 'pilot_id' => $pilot_id,
+ 'name' => $name,
+ 'password' => Hash::make($new_password),
+ 'api_key' => Utils::generateApiKey(),
+ 'airline_id' => $airline_id,
+ 'rank_id' => $rank_id,
+ 'home_airport_id' => $row->hub,
+ 'curr_airport_id' => $row->hub,
+ 'flights' => (int) $row->totalflights,
+ 'flight_time' => Utils::hoursToMinutes($row->totalhours),
+ 'state' => $state,
+ 'created_at' => $this->parseDate($row->joindate),
+ ];
+
+ $user = User::updateOrCreate(['email' => $row->email], $attrs);
+ $this->idMapper->addMapping('users', $row->pilotid, $user->id);
+
+ $this->updateUserRoles($user, $row->pilotid);
+
+ if ($user->wasRecentlyCreated) {
+ $count++;
+ }
+ }
+
+ $this->info('Imported '.$count.' users');
+ }
+
+ /**
+ * Update a user's roles and add them to the proper ones
+ *
+ * @param User $user
+ * @param string $old_pilot_id
+ */
+ protected function updateUserRoles(User $user, $old_pilot_id)
+ {
+ // Be default add them to the user role, and then determine if they
+ // belong to any other groups, and add them to that
+ $this->userSvc->addUserToRole($user, 'user');
+
+ // Figure out what other groups they belong to
+ }
+
+ /**
+ * Get the user's new state from their original state
+ *
+ * @param $state
+ *
+ * @return int
+ */
+ protected function getUserState($state)
+ {
+ // TODO: This state might differ between simpilot and classic version
+
+ $state = (int) $state;
+
+ // Declare array of classic states
+ $phpvms_classic_states = [
+ 'ACTIVE' => 0,
+ 'INACTIVE' => 1,
+ 'BANNED' => 2,
+ 'ON_LEAVE' => 3,
+ ];
+
+ // Decide which state they will be in accordance with v7
+ if ($state === $phpvms_classic_states['ACTIVE']) {
+ return UserState::ACTIVE;
+ }
+
+ if ($state === $phpvms_classic_states['INACTIVE']) {
+ // TODO: Make an inactive state?
+ return UserState::REJECTED;
+ }
+
+ if ($state === $phpvms_classic_states['BANNED']) {
+ return UserState::SUSPENDED;
+ }
+
+ if ($state === $phpvms_classic_states['ON_LEAVE']) {
+ return UserState::ON_LEAVE;
+ }
+
+ $this->error('Unknown status: '.$state);
+
+ return UserState::ACTIVE;
+ }
+}
diff --git a/modules/Installer/Services/Importer/Stages/Stage1.php b/modules/Installer/Services/Importer/Stages/Stage1.php
new file mode 100644
index 000000000..4c984747b
--- /dev/null
+++ b/modules/Installer/Services/Importer/Stages/Stage1.php
@@ -0,0 +1,69 @@
+cleanupDb();
+
+ // Run the first set of importers
+ parent::run();
+ }
+
+ /**
+ * Cleanup the local database of any users and other data that might conflict
+ * before running the importer
+ */
+ protected function cleanupDb()
+ {
+ $this->info('Running database cleanup/empty before starting');
+
+ Acars::truncate();
+ Bid::truncate();
+ Expense::truncate();
+ Pirep::truncate();
+ User::truncate();
+ UserAward::truncate();
+ File::truncate();
+ News::truncate();
+ Journal::truncate();
+ JournalTransaction::truncate();
+ Flight::truncate();
+ FlightField::truncate();
+ FlightFieldValue::truncate();
+
+ DB::table('flight_fare')->truncate();
+ DB::table('flight_subfleet')->truncate();
+ }
+}
diff --git a/modules/Installer/Services/Importer/Stages/Stage2.php b/modules/Installer/Services/Importer/Stages/Stage2.php
new file mode 100644
index 000000000..bdb46ffa7
--- /dev/null
+++ b/modules/Installer/Services/Importer/Stages/Stage2.php
@@ -0,0 +1,13 @@
+findLastPireps();
+ $this->recalculateUserStats();
+ }
+
+ /**
+ * Go through and set the last PIREP ID for the users
+ */
+ protected function findLastPireps()
+ {
+ }
+
+ /**
+ * Recalculate all of the user stats
+ */
+ protected function recalculateUserStats()
+ {
+ $this->comment('--- RECALCULATING USER STATS ---');
+ $userSvc = app(UserService::class);
+
+ User::all()->each(function ($user) use ($userSvc) {
+ $userSvc->recalculateStats($user);
+ });
+ }
+}
diff --git a/modules/Installer/Utils/IdMapper.php b/modules/Installer/Utils/IdMapper.php
new file mode 100644
index 000000000..2df6a59a8
--- /dev/null
+++ b/modules/Installer/Utils/IdMapper.php
@@ -0,0 +1,49 @@
+valueStore = Valuestore::make(storage_path('app/legacy_migration.json'));
+ }
+
+ /**
+ * Create a new mapping between an old ID and the new one
+ *
+ * @param string $entity Name of the entity (e,g table)
+ * @param string $old_id
+ * @param string $new_id
+ */
+ public function addMapping($entity, $old_id, $new_id)
+ {
+ $key_name = $entity.'_'.$old_id;
+ if (!$this->valueStore->has($key_name)) {
+ $this->valueStore->put($key_name, $new_id);
+ }
+ }
+
+ /**
+ * Return the ID for a mapping
+ *
+ * @param $entity
+ * @param $old_id
+ *
+ * @return bool
+ */
+ public function getMapping($entity, $old_id)
+ {
+ $key_name = $entity.'_'.$old_id;
+ if (!$this->valueStore->has($key_name)) {
+ return 0;
+ }
+
+ return $this->valueStore->get($key_name);
+ }
+}
diff --git a/modules/Installer/Utils/ImporterDB.php b/modules/Installer/Utils/ImporterDB.php
new file mode 100644
index 000000000..66a45bd0d
--- /dev/null
+++ b/modules/Installer/Utils/ImporterDB.php
@@ -0,0 +1,134 @@
+creds = $creds;
+ $this->dsn = 'mysql:'.implode(';', [
+ 'host='.$this->creds['host'],
+ 'port='.$this->creds['port'],
+ 'dbname='.$this->creds['name'],
+ ]);
+
+ Log::info('Using DSN: '.$this->dsn);
+ }
+
+ public function connect()
+ {
+ Log::info('Connection string: '.$this->dsn);
+
+ try {
+ $this->conn = new PDO($this->dsn, $this->creds['user'], $this->creds['pass']);
+ $this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
+ } catch (PDOException $e) {
+ Log::error($e);
+ exit();
+ }
+ }
+
+ /**
+ * Return the table name with the prefix
+ *
+ * @param $table
+ *
+ * @return string
+ */
+ public function tableName($table)
+ {
+ if ($this->creds['table_prefix'] !== false) {
+ return $this->creds['table_prefix'].$table;
+ }
+
+ return $table;
+ }
+
+ /**
+ * @param $table
+ *
+ * @return mixed
+ */
+ public function getTotalRows($table)
+ {
+ $this->connect();
+
+ $sql = 'SELECT COUNT(*) FROM '.$this->tableName($table);
+ $rows = $this->conn->query($sql)->fetchColumn();
+
+ Log::info('Found '.$rows.' rows in '.$table);
+
+ return (int) $rows;
+ }
+
+ /**
+ * Read all the rows in a table, but read them in a batched manner
+ *
+ * @param string $table The name of the table
+ * @param null $read_rows Number of rows to read
+ *
+ * @return \Generator
+ */
+ public function readRows($table, $read_rows = null)
+ {
+ $this->connect();
+
+ $offset = 0;
+ if ($read_rows === null) {
+ $read_rows = self::BATCH_READ_ROWS;
+ }
+
+ $total_rows = $this->getTotalRows($table);
+
+ while ($offset < $total_rows) {
+ $rows_to_read = $offset + $read_rows;
+ if ($rows_to_read > $total_rows) {
+ $rows_to_read = $total_rows;
+ }
+
+ Log::info('Reading '.$offset.' to '.$rows_to_read.' of '.$total_rows);
+
+ yield from $this->readRowsOffset($table, self::BATCH_READ_ROWS, $offset);
+
+ $offset += self::BATCH_READ_ROWS;
+ }
+ }
+
+ /**
+ * @param string $table
+ * @param int $limit Number of rows to read
+ * @param int $offset Where to start from
+ *
+ * @return \Generator
+ */
+ public function readRowsOffset($table, $limit, $offset)
+ {
+ $sql = 'SELECT * FROM '.$this->tableName($table).' LIMIT '.$limit.' OFFSET '.$offset;
+
+ try {
+ foreach ($this->conn->query($sql) as $row) {
+ yield $row;
+ }
+ } catch (PDOException $e) {
+ // Without incrementing the offset, it should re-run the same query
+ Log::error($e);
+
+ if (strpos($e->getMessage(), 'server has gone away') !== false) {
+ $this->connect();
+ }
+ }
+ }
+}
diff --git a/modules/Installer/Utils/LoggerTrait.php b/modules/Installer/Utils/LoggerTrait.php
new file mode 100644
index 000000000..f8faa60bb
--- /dev/null
+++ b/modules/Installer/Utils/LoggerTrait.php
@@ -0,0 +1,23 @@
+