Compare commits
29 Commits
eabf715bf2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 05ca994253 | |||
| 6fad966b7e | |||
| badb4dc46f | |||
| 3aba428d2a | |||
| a270913931 | |||
| a753bffaf8 | |||
| e24d3ceaec | |||
| 30e4d5ffb3 | |||
| 9dbacaa4ac | |||
| 0f12250644 | |||
| a6812ad95c | |||
| 906f8cda57 | |||
| 09841ba1f7 | |||
| 61ff0c5002 | |||
| 05c16147ee | |||
| 57b015eb18 | |||
| 150c34bfb8 | |||
| 10d6ee8dee | |||
| 3eb3971d79 | |||
| 3bd2bda84c | |||
| 05a6d1da0e | |||
| f05ea2fd97 | |||
| 1846cb6a6d | |||
| e1bed676e4 | |||
| 10b5b6a5c9 | |||
| 1d5b9f340f | |||
| 69d72e0912 | |||
| c7fe3268c7 | |||
| 016e752dcd |
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\UserAction;
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
#[Signature('app:flight-feed-update')]
|
||||
#[Description('Command description')]
|
||||
class FlightFeedUpdate extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$time = now('UTC')->startOfMinute();
|
||||
|
||||
$this->logFlightActions('flight_departing', UserFlight::where('departure_date', $time)->get());
|
||||
$this->logFlightActions('flight_arriving', UserFlight::where('arrival_date', $time)->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param Collection<UserFlight> $flights
|
||||
* @return void
|
||||
*/
|
||||
private function logFlightActions(string $type, Collection $flights): void
|
||||
{
|
||||
|
||||
foreach ($flights as $flight) {
|
||||
|
||||
UserAction::create([
|
||||
'user_id' => $flight->user_id,
|
||||
'type' => $type,
|
||||
'data' => [
|
||||
'flight' => $flight->snapshot($flight->id),
|
||||
]
|
||||
]);
|
||||
|
||||
if($type === 'flight_departing'){
|
||||
$flight->user->calculateAchievements();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,29 +2,50 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\DTOs\FlightStatData;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\IataEquipmentCode;
|
||||
use App\Models\Notification;
|
||||
use App\Models\UserFlight;
|
||||
use App\Services\FlightStatsService;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
#[Signature('app:update-departed-flights')]
|
||||
#[Description('Command description')]
|
||||
class UpdateDepartedFlights extends Command
|
||||
{
|
||||
|
||||
public function __construct(protected FlightStatsService $flightStats)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function notifyDataError(UserFlight $flight): void
|
||||
{
|
||||
Notification::create([
|
||||
'user_id' => $flight->user_id,
|
||||
'title' => "Auto update failed for {$flight->flight_number}",
|
||||
'body' => "There was an error fetching flight data for {$flight->flight_number}. Please manually check the aircraft type, registration and departure/arrival times.",
|
||||
'url' => '/flights/' . $flight->id . '/edit'
|
||||
]);
|
||||
|
||||
$flight->update(['auto_update' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): void
|
||||
{
|
||||
$now = now()->utc();
|
||||
$oneHourAgo = $now->copy()->subHours(1);
|
||||
|
||||
$userFlights = UserFlight::whereBetween('arrival_date', [
|
||||
$oneHourAgo->toDateTimeString(),
|
||||
$now->toDateTimeString(),
|
||||
])
|
||||
$userFlights = UserFlight::where('arrival_date', '<=', $now->copy()->subHour()->toDateTimeString())
|
||||
->where('auto_update', true)
|
||||
->whereNotNull('flight_number')
|
||||
->get();
|
||||
@@ -36,72 +57,96 @@ class UpdateDepartedFlights extends Command
|
||||
|
||||
if (empty($matches)) {
|
||||
$this->warn("Could not parse flight number: {$flight->flight_number}");
|
||||
$this->notifyDataError($flight);
|
||||
continue;
|
||||
}
|
||||
|
||||
$airlineCode = strtoupper($matches[1]);
|
||||
$airlineCode = strtoupper($matches[1]);
|
||||
$flightNumber = $matches[2];
|
||||
|
||||
$arrivalDate = $flight->arrival_date->setTimezone($flight->arrivalAirport->timezone);
|
||||
$year = $arrivalDate->year;
|
||||
$month = $arrivalDate->month;
|
||||
$day = $arrivalDate->day;
|
||||
|
||||
$url = "https://www.flightstats.com/v2/api-next/flight-tracker/{$airlineCode}/{$flightNumber}/{$year}/{$month}/{$day}";
|
||||
$data = $this->flightStats->fetchFlightData($airlineCode, $flightNumber, $arrivalDate);
|
||||
|
||||
$response = Http::get($url);
|
||||
|
||||
if (!$response->successful()) {
|
||||
$this->warn("Failed to fetch data for {$flight->flight_number}: HTTP {$response->status()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
$flightData = $data['data'] ?? [];
|
||||
|
||||
if (empty($flightData)) {
|
||||
if (!$data) {
|
||||
$this->warn("No flight data returned for {$airlineCode}{$flightNumber}");
|
||||
$this->notifyDataError($flight);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($data->departure_iata !== $flight->departureAirport->iata_code ||
|
||||
$data->arrival_iata !== $flight->arrivalAirport->iata_code) {
|
||||
$this->warn("Airport mismatch for {$airlineCode}{$flightNumber} — API: {$data->departure_iata}→{$data->arrival_iata}, expected: {$flight->departureAirport->iata_code}→{$flight->arrivalAirport->iata_code}");
|
||||
$this->notifyDataError($flight);
|
||||
|
||||
$tailNumber = $flightData['positional']['flexTrack']['tailNumber'] ?? null;
|
||||
$estimatedDepartureUtc = $flightData['schedule']['estimatedActualDepartureUTC'] ?? null;
|
||||
$estimatedArrivalUtc = $flightData['schedule']['estimatedActualArrivalUTC'] ?? null;
|
||||
$equipmentCode = $flightData['additionalFlightInfo']['equipment']['iata'] ?? null;
|
||||
|
||||
$apiDepartureIata = $data['data']['departureAirport']['iata'] ?? null;
|
||||
$apiArrivalIata = $data['data']['arrivalAirport']['iata'] ?? null;
|
||||
|
||||
if ($apiDepartureIata !== $flight->departureAirport->iata_code ||
|
||||
$apiArrivalIata !== $flight->arrivalAirport->iata_code) {
|
||||
$this->warn("Airport mismatch for {$airlineCode}{$flightNumber} — API: {$apiDepartureIata}→{$apiArrivalIata}, expected: {$flight->departureAirport->iata_code}→{$flight->arrivalAirport->iata_code}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
|
||||
if ($tailNumber && $tailNumber !== $flight->aircraft_registration) {
|
||||
$updates['aircraft_registration'] = $tailNumber;
|
||||
if ($data->aircraft_registration && $data->aircraft_registration !== $flight->aircraft_registration) {
|
||||
$updates['aircraft_registration'] = $data->aircraft_registration;
|
||||
}
|
||||
|
||||
if ($estimatedDepartureUtc && Carbon::parse($estimatedDepartureUtc)->ne($flight->departure_date)) {
|
||||
$updates['departure_date'] = Carbon::parse($estimatedDepartureUtc);
|
||||
if ($data->estimated_departure_utc?->ne($flight->departure_date)) {
|
||||
$updates['departure_date'] = $data->estimated_departure_utc;
|
||||
}
|
||||
|
||||
if ($estimatedArrivalUtc && Carbon::parse($estimatedArrivalUtc)->ne($flight->arrival_date)) {
|
||||
$updates['arrival_date'] = Carbon::parse($estimatedArrivalUtc);
|
||||
if ($data->estimated_arrival_utc?->ne($flight->arrival_date)) {
|
||||
$updates['arrival_date'] = $data->estimated_arrival_utc;
|
||||
}
|
||||
|
||||
if ($data->equipment_iata) {
|
||||
$currentAircraft = $flight->aircraft;
|
||||
|
||||
if ($currentAircraft?->iata_code !== $data->equipment_iata) {
|
||||
$match = $this->flightStats->guessAircraftFromIata($data->equipment_iata);
|
||||
|
||||
if ($match) {
|
||||
$updates['aircraft_id'] = $match->id;
|
||||
} else {
|
||||
Log::info("No aircraft match for IATA code {$data->equipment_iata} on flight {$airlineCode}{$flightNumber}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($updates)) {
|
||||
$flight->update($updates);
|
||||
$this->info("Updated flight {$airlineCode}{$flightNumber}: " . implode(', ', array_keys($updates)));
|
||||
|
||||
$changeDescriptions = [];
|
||||
if (isset($updates['aircraft_registration'])) {
|
||||
$changeDescriptions[] = "Registration updated to {$updates['aircraft_registration']}";
|
||||
}
|
||||
if (isset($updates['departure_date'])) {
|
||||
$changeDescriptions[] = "Departure updated to {$updates['departure_date']}";
|
||||
}
|
||||
if (isset($updates['arrival_date'])) {
|
||||
$changeDescriptions[] = "Arrival updated to {$updates['arrival_date']}";
|
||||
}
|
||||
if (isset($updates['aircraft_id'])) {
|
||||
$aircraft = Aircraft::find($updates['aircraft_id']);
|
||||
$changeDescriptions[] = "Aircraft type updated to {$aircraft->display_name_short}";
|
||||
}
|
||||
|
||||
Notification::create([
|
||||
'user_id' => $flight->user_id,
|
||||
'title' => "Flight {$airlineCode}{$flightNumber} updated",
|
||||
'body' => implode("\n", $changeDescriptions),
|
||||
'url' => '/u/'. $flight->user->name . '/flight/'. $flight->id,
|
||||
]);
|
||||
} else {
|
||||
$this->info("No changes for {$airlineCode}{$flightNumber}");
|
||||
|
||||
Notification::create([
|
||||
'user_id' => $flight->user_id,
|
||||
'title' => "Flight {$airlineCode}{$flightNumber} updated — no changes",
|
||||
'body' => "Your flight was completed and no updates were made to aircraft, registration, or departure/arrival times.",
|
||||
'url' => '/u/'. $flight->user->name . '/flight/'. $flight->id,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->info("Flight {$airlineCode}{$flightNumber} — Tail: {$tailNumber}, Equipment: {$equipmentCode}");
|
||||
$this->info("Departure: {$estimatedDepartureUtc} | Arrival: {$estimatedArrivalUtc}");
|
||||
|
||||
$flight->update(['auto_update' => false]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\DTOs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
readonly class FlightStatData
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $aircraft_registration,
|
||||
public ?Carbon $estimated_departure_utc,
|
||||
public ?Carbon $estimated_arrival_utc,
|
||||
public ?string $equipment_iata,
|
||||
public ?string $departure_iata,
|
||||
public ?string $arrival_iata,
|
||||
public array $airline_fs_codes,
|
||||
public ?string $operating_fs,
|
||||
) {}
|
||||
|
||||
public static function fromApiResponse(array $flightData): self
|
||||
{
|
||||
$primaryFs = $flightData['resultHeader']['carrier']['fs'] ?? null;
|
||||
$operatingFs = $flightData['positional']['flexTrack']['carrierFsCode'] ?? null;
|
||||
$codeshares = array_column($flightData['codeshares'] ?? [], 'fs');
|
||||
|
||||
$fsCodes = array_values(array_unique(array_filter([
|
||||
$primaryFs,
|
||||
$operatingFs,
|
||||
...$codeshares,
|
||||
])));
|
||||
|
||||
return new self(
|
||||
aircraft_registration: $flightData['positional']['flexTrack']['tailNumber'] ?? null,
|
||||
estimated_departure_utc: isset($flightData['schedule']['estimatedActualDepartureUTC'])
|
||||
? Carbon::parse($flightData['schedule']['estimatedActualDepartureUTC'])
|
||||
: null,
|
||||
estimated_arrival_utc: isset($flightData['schedule']['estimatedActualArrivalUTC'])
|
||||
? Carbon::parse($flightData['schedule']['estimatedActualArrivalUTC'])
|
||||
: null,
|
||||
equipment_iata: $flightData['additionalFlightInfo']['equipment']['iata'] ?? null,
|
||||
departure_iata: $flightData['departureAirport']['iata'] ?? null,
|
||||
arrival_iata: $flightData['arrivalAirport']['iata'] ?? null,
|
||||
airline_fs_codes: $fsCodes,
|
||||
operating_fs: $flightData['positional']['flexTrack']['carrierFsCode'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\DTOs;
|
||||
|
||||
readonly class MissingLivery
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $airline_name,
|
||||
public readonly string $aircraft_display_name,
|
||||
public readonly string $filename,
|
||||
public readonly string $clipboard_text,
|
||||
) {}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Achievement;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AchievementController extends Controller
|
||||
{
|
||||
public function index(User $user)
|
||||
{
|
||||
$achievements = Achievement::with(['category', 'difficulty'])
|
||||
->get()
|
||||
->groupBy(fn(Achievement $a) => $a->category->name)
|
||||
->map(fn($group) => $group->sortBy('id')->values());
|
||||
|
||||
$userAchievements = $user->achievements()
|
||||
->with('achievement')
|
||||
->orderBy('achievement_id')
|
||||
->get()
|
||||
->keyBy('achievement_id');
|
||||
|
||||
return Inertia::render('UserAchievements', [
|
||||
'user' => $user,
|
||||
'canEdit' => auth()->id() === $user->id,
|
||||
'isFollowing' => auth()->check() && auth()->user()->isFollowing($user),
|
||||
'achievements' => $achievements,
|
||||
'userAchievements' => $userAchievements,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
||||
use App\Models\IgnoredMissingLivery;
|
||||
use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use App\Services\AdminService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(private readonly AdminService $adminService){}
|
||||
|
||||
function staticAdminData(){
|
||||
return [
|
||||
'missingLiveryCount' => $this->adminService->getMissingLiveries()->count(),
|
||||
];
|
||||
}
|
||||
|
||||
function dashboard(){
|
||||
return inertia('Admin/Dashboard', [
|
||||
'title' => 'Admin Control Panel',
|
||||
'userCount' => User::count(),
|
||||
'oneWeekUserGrowth' => User::where('created_at', '>=', Carbon::now()->subWeek())->count(),
|
||||
'flightCount' => UserFlight::count(),
|
||||
'oneWeekFlightGrowth' => UserFlight::where('created_at', '>=', Carbon::now()->subWeek())->count(),
|
||||
'latestUser' => User::latest()->first()->name,
|
||||
...$this->staticAdminData(),
|
||||
]);
|
||||
}
|
||||
|
||||
function reconcileMissingLiveries(){
|
||||
$missingLiveries = $this->adminService->getMissingLiveries();
|
||||
|
||||
return Inertia::render('Admin/MissingLiveries', [
|
||||
'title' => $missingLiveries->count() . ' Missing Liveries',
|
||||
'missingLiveries' => $missingLiveries,
|
||||
...$this->staticAdminData(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function ignoreMissingLivery(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'filename' => 'required|string',
|
||||
]);
|
||||
|
||||
IgnoredMissingLivery::createOrFirst($validated);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminToolsController extends Controller
|
||||
{
|
||||
function missingLiveries(){
|
||||
|
||||
|
||||
/* $existingFiles = collect(glob(public_path('img/liveries/generated/*')))
|
||||
->map(fn ($path) => pathinfo($path, PATHINFO_FILENAME))
|
||||
->toArray();*/
|
||||
|
||||
$existingFiles = collect(glob('C:\\Users\\josh\\WebstormProjects\\Watermark-Remover\\images\\liveries_processed\\*'))
|
||||
->map(fn ($path) => pathinfo($path, PATHINFO_FILENAME))
|
||||
->toArray();
|
||||
|
||||
|
||||
$combos = \App\Models\UserFlight::with(['aircraft', 'airline'])
|
||||
->select('airline_id', 'aircraft_id')
|
||||
->whereNotNull('airline_id')
|
||||
->whereNotNull('aircraft_id')
|
||||
->distinct()
|
||||
->get()
|
||||
->filter(fn ($flight) => $flight->aircraft && $flight->airline)
|
||||
->map(fn ($flight) => [
|
||||
'airline_name' => $flight->airline->name,
|
||||
'aircraft_display_name' => $flight->aircraft->display_name,
|
||||
'filename' => $flight->airline->internal_name . '_' . $flight->aircraft->designator,
|
||||
])
|
||||
->filter(fn ($combo) => !in_array($combo['filename'], $existingFiles))
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'count' => $combos->count(),
|
||||
'liveries' => $combos,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AircraftApiController extends Controller
|
||||
{
|
||||
public function getLivery(string $aircraftDesignator){
|
||||
$path = "images/livery_templates/{$aircraftDesignator}.png";
|
||||
return $this->imageIfExists($path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,6 +47,12 @@ class AirlineApiController extends ApiController
|
||||
return $this->getAirlineLogo($airline);
|
||||
}
|
||||
|
||||
public function getLivery(string $airlineInternalName, string $aircraftDesignator){
|
||||
$path = "images/liveries/{$airlineInternalName}_{$aircraftDesignator}.png";
|
||||
|
||||
return $this->imageIfExists($path);
|
||||
}
|
||||
|
||||
function parseAirlineData(Airline $airline){
|
||||
$countryCode = $airline->country->code;
|
||||
|
||||
@@ -62,7 +68,7 @@ class AirlineApiController extends ApiController
|
||||
}
|
||||
|
||||
function getByCode(string $code){
|
||||
$lookupColumn = strlen($code) === 3 ? 'ICAO_code' : 'IATA_code';
|
||||
$lookupColumn = strlen($code) === 3 ? 'icao_code' : 'iata_code';
|
||||
$airlines = Airline::where($lookupColumn, strtoupper($code))->get()->map(fn($airline) => $this->parseAirlineData($airline));
|
||||
return response()->json($airlines);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserApiController extends ApiController
|
||||
{
|
||||
@@ -36,10 +37,12 @@ class UserApiController extends ApiController
|
||||
'departureCity' => $flight->departureAirport->municipality,
|
||||
'departureDateReadable' => $departure->format('F j'),
|
||||
'departureTime' => $departure->format('H:i'),
|
||||
'departureDateUtc' => $flight->departure_date,
|
||||
'arrivalAirportCode' => $flight->arrivalAirport->iata_code,
|
||||
'arrivalCity' => $flight->arrivalAirport->municipality,
|
||||
'arrivalDateReadable' => $arrival->format('F j'),
|
||||
'arrivalTime' => $arrival->format('H:i'),
|
||||
'arrivalDateUtc' => $flight->arrival_date,
|
||||
'flightNumber' => $flight->flight_number,
|
||||
'airlineName' => $flight->airline->name,
|
||||
'aircraftType' => $flight->aircraft->manufacturer_code . ' ' . $flight->aircraft->model_full_name,
|
||||
@@ -47,14 +50,4 @@ class UserApiController extends ApiController
|
||||
]);
|
||||
}
|
||||
|
||||
public function flights(string $username): JsonResponse
|
||||
{
|
||||
$user = User::where('name', 'ilike', $username)->first();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json(['message' => 'User not found'], 404);
|
||||
}
|
||||
|
||||
return response()->json($user->FlightController()->flights());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class AuthenticatedSessionController extends Controller
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
return redirect()->intended(route('home', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,23 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
public function imageIfExists(string $path, $cacheLimit = 60 * 60 * 72){
|
||||
if(!Storage::disk('local')->exists($path)){
|
||||
return response()->json(['error' => 'Image not found'], 404);
|
||||
}
|
||||
|
||||
$fullPath = Storage::disk('local')->path($path);
|
||||
$lastModified = filemtime($fullPath);
|
||||
|
||||
return response()->file($fullPath, [
|
||||
'Content-Type' => 'image/png',
|
||||
'Cache-Control' => 'public, max-age='.$cacheLimit, // 24 hours
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $lastModified) . ' GMT',
|
||||
'ETag' => md5($path . $lastModified),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ use App\Models\Airport;
|
||||
use App\Models\CrewType;
|
||||
use App\Models\FlightClass;
|
||||
use App\Models\FlightReason;
|
||||
use App\Models\IataEquipmentCode;
|
||||
use App\Models\SeatType;
|
||||
use App\Models\UserAction;
|
||||
use App\Models\UserFlight;
|
||||
use App\Services\FlightStatsService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -24,7 +26,24 @@ class FlightController extends Controller
|
||||
return [
|
||||
'flight_number' => ['nullable', 'string', 'max:10'],
|
||||
'departure_date' => ['required', 'date'],
|
||||
'arrival_date' => ['required', 'date'],
|
||||
'arrival_date' => ['required', 'date', function ($attribute, $value, $fail) {
|
||||
$from = request()->input('from_id');
|
||||
$to = request()->input('to_id');
|
||||
|
||||
if (!$from || !$to) return;
|
||||
|
||||
$departureAirport = Airport::find($from);
|
||||
$arrivalAirport = Airport::find($to);
|
||||
|
||||
if (!$departureAirport || !$arrivalAirport) return;
|
||||
|
||||
$departureUtc = Carbon::createFromFormat('Y-m-d\TH:i', request()->input('departure_date'), $departureAirport->timezone)->utc();
|
||||
$arrivalUtc = Carbon::createFromFormat('Y-m-d\TH:i', $value, $arrivalAirport->timezone)->utc();
|
||||
|
||||
if ($arrivalUtc->lessThanOrEqualTo($departureUtc)) {
|
||||
$fail('The arrival time must be after the departure time, accounting for time zones.');
|
||||
}
|
||||
}],
|
||||
'from_id' => ['required', 'integer', 'exists:airports,id'],
|
||||
'to_id' => ['required', 'integer', 'exists:airports,id'],
|
||||
'airline_id' => ['nullable', 'integer', 'exists:airlines,id'],
|
||||
@@ -44,24 +63,74 @@ class FlightController extends Controller
|
||||
{
|
||||
$number = strtoupper(trim($request->query('number', '')));
|
||||
|
||||
// Extract the airline code prefix — letters at the start e.g. "QF" from "QF1"
|
||||
preg_match('/^([A-Z]{2,3})/', $number, $matches);
|
||||
$code = $matches[1] ?? null;
|
||||
$isIata = strlen($code) === 2;
|
||||
$codeColumn = $isIata ? 'IATA_code' : 'ICAO_code';
|
||||
preg_match('/^([A-Z]{2,3})(\d+)/', $number, $matches);
|
||||
$code = $matches[1] ?? null;
|
||||
$flightNumber = $matches[2] ?? null;
|
||||
$isIata = strlen($code) === 2;
|
||||
$codeColumn = $isIata ? 'iata_code' : 'icao_code';
|
||||
|
||||
$apiAirlineCodes = [];
|
||||
$fromOptions = [];
|
||||
$toOptions = [];
|
||||
$aircraftOptions = [];
|
||||
|
||||
if (strlen($number) >= 3 && $isIata) {
|
||||
$flightStatsApi = new FlightStatsService();
|
||||
$flightData = $flightStatsApi->fetchFlightData($code, $flightNumber);
|
||||
|
||||
if ($flightData) {
|
||||
$apiAirlineCodes = $flightData->airline_fs_codes;
|
||||
|
||||
$fromOptions = Airport::where('iata_code', $flightData->departure_iata)
|
||||
->get()
|
||||
->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'country_code' => strtolower($a->region->country->code)])
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
$toOptions = Airport::where('iata_code', $flightData->arrival_iata)
|
||||
->get()
|
||||
->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'country_code' => strtolower($a->region->country->code)])
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
if($flightData->equipment_iata){
|
||||
$equipment = IataEquipmentCode::where('iata_code', $flightData->equipment_iata)->first();
|
||||
if ($equipment) {
|
||||
$bestGuess = $flightStatsApi->guessAircraftFromIata($flightData->equipment_iata);
|
||||
|
||||
$aircraftOptions = Aircraft::where('designator', $equipment->icao_code)
|
||||
->get()
|
||||
->sortBy(fn($a) => $a->id === $bestGuess?->id ? 0 : 1)
|
||||
->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Airlines from the typed code + any additional codes from the API, merged and deduped by id
|
||||
$allCodes = array_unique(array_filter([$code, ...$apiAirlineCodes]));
|
||||
|
||||
$airlines = Airline::where(function ($q) use ($codeColumn, $code, $allCodes) {
|
||||
$q->whereIn($codeColumn, $allCodes);
|
||||
})
|
||||
->get()
|
||||
->unique('id')
|
||||
->sortBy(function ($a) use ($code, $flightData) {
|
||||
if ($a->iata_code === $flightData?->operating_fs) return 0;
|
||||
if ($a->iata_code === $code) return 1;
|
||||
return 2;
|
||||
})
|
||||
->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'logo_url' => $a->logo_url])
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
$airlines = $code
|
||||
? Airline::where($codeColumn, $code)
|
||||
->get()
|
||||
->map(fn ($airline) => ['value' => $airline->id, 'title' => $airline->display_name, 'logo_url' => $airline->logo_url])
|
||||
->values()
|
||||
->toArray()
|
||||
: collect()->toArray();
|
||||
return response()->json([
|
||||
'airline_options' => $airlines,
|
||||
'from_options' => [],
|
||||
'to_options' => [],
|
||||
'aircraft_options' => [],
|
||||
'from_options' => $fromOptions,
|
||||
'to_options' => $toOptions,
|
||||
'aircraft_options' => $aircraftOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -203,10 +272,10 @@ class FlightController extends Controller
|
||||
$updated = $flight->snapshot($flight->id);
|
||||
$this->recordChanges($flight, $dirty, $original, $updated);
|
||||
|
||||
return redirect()->route('profile.departure-board', [Auth::user()->name, $flight->id]);
|
||||
return redirect()->route('profile.departure-board', [$flight->user->name, $flight->id]);
|
||||
}
|
||||
|
||||
public function delete(UserFlight $flight)
|
||||
public function delete(UserFlight $flight, ?string $referrer = 'departure-board')
|
||||
{
|
||||
$this->authorize('delete', $flight);
|
||||
|
||||
@@ -227,7 +296,7 @@ class FlightController extends Controller
|
||||
]);
|
||||
|
||||
$flight->delete();
|
||||
return redirect()->route('profile.departure-board', [Auth::user()->name]);
|
||||
return redirect()->route('profile.'.$referrer, [Auth::user()->name]);
|
||||
}
|
||||
|
||||
public function staticData() : array {
|
||||
|
||||
@@ -35,37 +35,21 @@ class FlightImportController extends Controller
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getPossibleAircraft(string $aircraftQuery) {
|
||||
public function getPossibleAircraft(string $aircraftQuery): array
|
||||
{
|
||||
preg_match('/\((\w+)\)/', $aircraftQuery, $matches);
|
||||
$designator = $matches[1] ?? null;
|
||||
|
||||
$sortOverrides = [
|
||||
'B788' => "CASE WHEN model_full_name ILIKE '%BBJ%' THEN 1 ELSE 0 END",
|
||||
'B789' => "CASE WHEN model_full_name ILIKE '%BBJ%' THEN 1 ELSE 0 END",
|
||||
];
|
||||
if (!$designator) return [];
|
||||
|
||||
if(!$designator){
|
||||
$aircraft = [];
|
||||
} else {
|
||||
|
||||
$aircraft = Aircraft::when($designator, fn($query) => $query->where('designator', 'ilike', $designator))
|
||||
->when(
|
||||
isset($sortOverrides[$designator]),
|
||||
fn($q) => $q->orderByRaw($sortOverrides[$designator])
|
||||
)
|
||||
->orderBy('model_full_name')
|
||||
->limit(10)
|
||||
->get(['id', 'manufacturer_code', 'model_full_name', 'designator'])
|
||||
->map(fn($aircraft) => [
|
||||
'value' => $aircraft->id,
|
||||
'title' => $aircraft->display_name,
|
||||
])
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
}
|
||||
|
||||
return $aircraft;
|
||||
return Aircraft::where('designator', 'ilike', $designator)
|
||||
->orderByDesc('preferred')
|
||||
->orderBy('model_full_name')
|
||||
->limit(10)
|
||||
->get(['id', 'manufacturer_code', 'model_full_name', 'designator', 'preferred'])
|
||||
->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getPossibleAirports(string $airportQuery) {
|
||||
@@ -116,20 +100,20 @@ class FlightImportController extends Controller
|
||||
$airlines = Airline::when($iata || $icao, function ($query) use ($iata, $icao) {
|
||||
$query->orderByRaw("
|
||||
CASE
|
||||
WHEN \"IATA_code\" = ? AND \"ICAO_code\" = ? THEN 0
|
||||
WHEN \"IATA_code\" = ? THEN 1
|
||||
WHEN \"ICAO_code\" = ? THEN 2
|
||||
WHEN \"iata_code\" = ? AND \"icao_code\" = ? THEN 0
|
||||
WHEN \"iata_code\" = ? THEN 1
|
||||
WHEN \"icao_code\" = ? THEN 2
|
||||
ELSE 3
|
||||
END
|
||||
", [$iata, $icao, $iata, $icao])
|
||||
->where(function ($q) use ($iata, $icao) {
|
||||
$q->where('IATA_code', $iata)
|
||||
->orWhere('ICAO_code', $icao);
|
||||
$q->where('iata_code', $iata)
|
||||
->orWhere('icao_code', $icao);
|
||||
});
|
||||
})
|
||||
->orderByDesc('active')
|
||||
->limit(10)
|
||||
->get(['id', 'name', 'IATA_code', 'ICAO_code', 'internal_name'])
|
||||
->get(['id', 'name', 'iata_code', 'icao_code', 'internal_name'])
|
||||
->map(fn($airline) => [
|
||||
'value' => $airline->id,
|
||||
'title' => $airline->display_name,
|
||||
@@ -142,6 +126,18 @@ class FlightImportController extends Controller
|
||||
return $airlines;
|
||||
}
|
||||
|
||||
|
||||
public function showFr24Import()
|
||||
{
|
||||
if (Auth::user()->importedFlights()->exists()) {
|
||||
return to_route('reconcile');
|
||||
}
|
||||
|
||||
return Inertia::render('Fr24Import');
|
||||
}
|
||||
|
||||
// FlightImportController.php
|
||||
|
||||
public function reconcile(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
@@ -149,36 +145,38 @@ class FlightImportController extends Controller
|
||||
$flightToReconcile = ImportedFlight::where('user_id', $user->id)->orderBy('date', 'asc')->first();
|
||||
|
||||
if (!$flightToReconcile) {
|
||||
return null;
|
||||
return to_route('import.fr24');
|
||||
}
|
||||
|
||||
$date = null;
|
||||
if ($flightToReconcile->date) {
|
||||
$date = Carbon::createFromFormat('Y-m-d', $flightToReconcile->date)->format('Y-m-d');
|
||||
}
|
||||
$date = $flightToReconcile->date
|
||||
? Carbon::createFromFormat('Y-m-d', $flightToReconcile->date)->format('Y-m-d')
|
||||
: null;
|
||||
|
||||
|
||||
return [
|
||||
'imported_flight_id' => $flightToReconcile->id,
|
||||
'flight_classes' => $this->selectOptions(FlightClass::class),
|
||||
'flight_reasons' => $this->selectOptions(FlightReason::class),
|
||||
'seat_types' => $this->selectOptions(SeatType::class),
|
||||
'flight_number' => $flightToReconcile->flight_number ?? '',
|
||||
'date' => $date ?? '',
|
||||
'dep_time' => $this->formatTime($flightToReconcile->dep_time),
|
||||
'arr_time' => $this->formatTime($flightToReconcile->arr_time),
|
||||
'duration' => $this->formatTime($flightToReconcile->duration),
|
||||
'registration' => $flightToReconcile->registration ?? '',
|
||||
'note' => $flightToReconcile->note ?? '',
|
||||
'seat_number' => $flightToReconcile->seat_number ?? '',
|
||||
'flight_class' => $flightToReconcile->flight_class ?? '',
|
||||
'seat_type' => $flightToReconcile->seat_type ?? '',
|
||||
'flight_reason' => $flightToReconcile->flight_reason ?? '',
|
||||
'airline_options' => $this->getPossibleAirlines($flightToReconcile->airline ?? ''),
|
||||
'to_options' => $this->getPossibleAirports($flightToReconcile->to ?? ''),
|
||||
'from_options' => $this->getPossibleAirports($flightToReconcile->from ?? ''),
|
||||
'aircraft_options' => $this->getPossibleAircraft($flightToReconcile->aircraft ?? ''),
|
||||
$flight = [
|
||||
'imported_flight_id' => $flightToReconcile->id,
|
||||
'flight_classes' => $this->selectOptions(FlightClass::class),
|
||||
'flight_reasons' => $this->selectOptions(FlightReason::class),
|
||||
'seat_types' => $this->selectOptions(SeatType::class),
|
||||
'flight_number' => $flightToReconcile->flight_number ?? '',
|
||||
'date' => $date ?? '',
|
||||
'dep_time' => $this->formatTime($flightToReconcile->dep_time),
|
||||
'arr_time' => $this->formatTime($flightToReconcile->arr_time),
|
||||
'duration' => $this->formatTime($flightToReconcile->duration),
|
||||
'registration' => $flightToReconcile->registration ?? '',
|
||||
'note' => $flightToReconcile->note ?? '',
|
||||
'flight_class' => $flightToReconcile->flight_class !== null ? (int) $flightToReconcile->flight_class : null,
|
||||
'seat_type' => $flightToReconcile->seat_type !== null ? (int) $flightToReconcile->seat_type : null,
|
||||
'flight_reason' => $flightToReconcile->flight_reason !== null ? (int) $flightToReconcile->flight_reason : null,
|
||||
'airline_options' => $this->getPossibleAirlines($flightToReconcile->airline ?? ''),
|
||||
'to_options' => $this->getPossibleAirports($flightToReconcile->to ?? ''),
|
||||
'from_options' => $this->getPossibleAirports($flightToReconcile->from ?? ''),
|
||||
'aircraft_options' => $this->getPossibleAircraft($flightToReconcile->aircraft ?? ''),
|
||||
];
|
||||
|
||||
return Inertia::render('ReconcileFlight', [
|
||||
'flight' => $flight,
|
||||
'key' => $flight['imported_flight_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use App\Http\Resources\UserFlightResource;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class FlightProfileController extends Controller
|
||||
{
|
||||
public function profileData(User $user, string $view, ?int $selectedFlightId = null) : array {
|
||||
return [
|
||||
'user' => $user,
|
||||
'canEdit' => auth()->check() && auth()->id() === $user->id,
|
||||
'initialView' => $view,
|
||||
'selectedFlightId' => $selectedFlightId,
|
||||
'flight_api_url' => '/data/user/'.$user->name.'/flights',
|
||||
'isFollowing' => auth()->check() && auth()->user()->isFollowing($user),
|
||||
];
|
||||
}
|
||||
|
||||
public function departureBoard(User $user, ?UserFlight $flight = null){
|
||||
$profileData = $this->profileData($user, 'board', $flight?->id);
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function map(User $user){
|
||||
$profileData = $this->profileData($user, 'map');
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function boardingPasses(User $user){
|
||||
$profileData = $this->profileData($user, 'passes');
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function view(User $user)
|
||||
{
|
||||
return $this->departureBoard($user);
|
||||
}
|
||||
|
||||
public function flight(User $user, UserFlight $userFlight)
|
||||
{
|
||||
if($userFlight->user_id !== $user->id){
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
||||
return Inertia::render('UserFlight', [
|
||||
'flightCount' => $user->flights()->count(),
|
||||
'flight' => $userFlight->snapshot($userFlight->id),
|
||||
'user' => $user,
|
||||
'isFollowing' => auth()->check() && auth()->user()->isFollowing($user),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Followee;
|
||||
use App\Models\Notification;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FollowerController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$followers = Followee::with('user')
|
||||
->where('followee_id', auth()->id())
|
||||
->orderBy('verified') // unverified first
|
||||
->get()
|
||||
->map(fn (Followee $f) => [
|
||||
'user' => $f->user,
|
||||
'verified' => $f->verified,
|
||||
]);
|
||||
|
||||
return response()->json($followers);
|
||||
}
|
||||
|
||||
public function approve(User $follower): JsonResponse
|
||||
{
|
||||
$followee = Followee::where('user_id', $follower->id)
|
||||
->where('followee_id', auth()->id())
|
||||
->pending()
|
||||
->firstOrFail();
|
||||
|
||||
$followee->update(['verified' => true]);
|
||||
|
||||
Notification::create([
|
||||
'user_id' => $follower->id,
|
||||
'title' => 'Follow request accepted',
|
||||
'body' => auth()->user()->name . ' accepted your follow request.',
|
||||
'is_achievement' => false,
|
||||
'url' => '/u/' . auth()->user()->name,
|
||||
]);
|
||||
|
||||
return response()->json(['status' => 'approved']);
|
||||
}
|
||||
|
||||
public function deny(User $follower): JsonResponse
|
||||
{
|
||||
Followee::where('user_id', $follower->id)
|
||||
->where('followee_id', auth()->id())
|
||||
->pending()
|
||||
->delete();
|
||||
|
||||
return response()->json(['status' => 'denied']);
|
||||
}
|
||||
|
||||
public function remove(User $follower): JsonResponse
|
||||
{
|
||||
Followee::where('user_id', $follower->id)
|
||||
->where('followee_id', auth()->id())
|
||||
->verified()
|
||||
->delete();
|
||||
|
||||
return response()->json(['status' => 'removed']);
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,34 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$unread = $user->notifications()
|
||||
->whereNull('read_at')
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
if ($unread->isNotEmpty()) {
|
||||
return response()->json($unread);
|
||||
}
|
||||
|
||||
$recent = $user->notifications()
|
||||
->latest()
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return response()->json($recent);
|
||||
}
|
||||
|
||||
public function markRead(Request $request, Notification $notification)
|
||||
{
|
||||
$this->authorize('update', $notification);
|
||||
|
||||
@@ -16,13 +16,13 @@ class SearchController extends Controller
|
||||
->where(function ($query) use ($q) {
|
||||
$len = strlen($q);
|
||||
if ($len === 2) {
|
||||
$query->where('IATA_code', 'ilike', $q);
|
||||
$query->where('iata_code', 'ilike', $q);
|
||||
} elseif ($len === 3) {
|
||||
$query->where('ICAO_code', 'ilike', $q);
|
||||
$query->where('icao_code', 'ilike', $q);
|
||||
} else {
|
||||
$query->where('name', 'ilike', "%{$q}%")
|
||||
->orWhere('IATA_code', 'ilike', "%{$q}%")
|
||||
->orWhere('ICAO_code', 'ilike', "%{$q}%");
|
||||
->orWhere('iata_code', 'ilike', "%{$q}%")
|
||||
->orWhere('icao_code', 'ilike', "%{$q}%");
|
||||
}
|
||||
})
|
||||
->limit(50)
|
||||
@@ -44,7 +44,7 @@ class SearchController extends Controller
|
||||
->orWhereRaw("CONCAT(manufacturer_code, ' ', model_full_name) ilike ?", ["%{$q}%"])
|
||||
->orWhereRaw("CONCAT(manufacturer_code, ' ', model_full_name) ilike ?", ["%{$replacedQuery}%"])
|
||||
->limit(200)
|
||||
->orderBy('id', 'asc')
|
||||
->orderBy('preferred', 'desc')
|
||||
->get(['id', 'manufacturer_code', 'model_full_name', 'designator'])
|
||||
->map(fn($aircraft) => [
|
||||
'value' => $aircraft->id,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Settings\SettingsRegistry;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
public function show(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$current = array_merge(SettingsRegistry::defaults(), $user->settings ?? []);
|
||||
$schema = SettingsRegistry::schema();
|
||||
|
||||
$fields = array_map(fn($field) => array_merge($field, [
|
||||
'value' => $current[$field['key']] ?? $field['default'],
|
||||
]), $schema);
|
||||
|
||||
return response()->json(['fields' => $fields]);
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$validated = $request->validate(SettingsRegistry::validationRules());
|
||||
$request->user()->updateSettings($validated['settings']);
|
||||
return response()->json(['message' => 'Settings saved.']);
|
||||
}
|
||||
|
||||
public function updateSingle(Request $request, string $key)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'value' => ['required'],
|
||||
]);
|
||||
|
||||
$request->user()->updateSetting($key, $validated['value']);
|
||||
|
||||
return response()->json(['message' => 'Setting saved.']);
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,93 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Followee;
|
||||
use App\Models\Notification;
|
||||
use App\Models\User;
|
||||
use App\Settings\SettingsRegistry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function follow(User $user): JsonResponse
|
||||
{
|
||||
abort_if($user->id === auth()->id(), 403);
|
||||
|
||||
$existing = Followee::where('user_id', auth()->id())
|
||||
->where('followee_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
return response()->json(['following' => false]);
|
||||
return response()->json(['status' => 'none']);
|
||||
}
|
||||
|
||||
$canView = Gate::allows('viewProfileData', $user);
|
||||
|
||||
Followee::create([
|
||||
'user_id' => auth()->id(),
|
||||
'followee_id' => $user->id,
|
||||
'verified' => $canView,
|
||||
]);
|
||||
|
||||
return response()->json(['following' => true]);
|
||||
Notification::create([
|
||||
'user_id' => $user->id,
|
||||
'title' => $canView ? 'New follower' : 'Follow request',
|
||||
'body' => $canView
|
||||
? auth()->user()->name . ' is now following you.'
|
||||
: auth()->user()->name . ' wants to follow you.',
|
||||
'is_achievement' => false,
|
||||
'url' => $canView ? '/u/' . auth()->user()->name : '/follow-requests',
|
||||
]);
|
||||
|
||||
return response()->json(['status' => $canView ? 'following' : 'requested']);
|
||||
}
|
||||
|
||||
public function approveRequest(User $follower): JsonResponse
|
||||
{
|
||||
$followee = Followee::where('user_id', $follower->id)
|
||||
->where('followee_id', auth()->id())
|
||||
->pending()
|
||||
->firstOrFail();
|
||||
|
||||
$followee->update(['verified' => true]);
|
||||
|
||||
Notification::create([
|
||||
'user_id' => $follower->id,
|
||||
'title' => 'Follow request accepted',
|
||||
'body' => auth()->user()->name . ' accepted your follow request.',
|
||||
'is_achievement' => false,
|
||||
'url' => '/u/' . auth()->user()->name,
|
||||
]);
|
||||
|
||||
return response()->json(['approved' => true]);
|
||||
}
|
||||
|
||||
public function denyRequest(User $follower): JsonResponse
|
||||
{
|
||||
Followee::where('user_id', $follower->id)
|
||||
->where('followee_id', auth()->id())
|
||||
->pending()
|
||||
->delete();
|
||||
|
||||
return response()->json(['denied' => true]);
|
||||
}
|
||||
|
||||
public function settings(?string $category = null){
|
||||
$allowedTabs = ['general', 'followers'];
|
||||
$user = auth()->user();
|
||||
$current = array_merge(SettingsRegistry::defaults(), $user->settings ?? []);
|
||||
$fields = array_map(fn($field) => array_merge($field, [
|
||||
'value' => $current[$field['key']] ?? $field['default'],
|
||||
]), SettingsRegistry::schema());
|
||||
|
||||
return Inertia::render('UserSettings', [
|
||||
'fields' => $fields,
|
||||
'categories' => SettingsRegistry::categories(),
|
||||
'defaultTab' => in_array($category, $allowedTabs, true) ? $category : 'general',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,32 +5,54 @@ namespace App\Http\Controllers;
|
||||
use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class UserFlightController extends Controller
|
||||
{
|
||||
|
||||
protected User $user;
|
||||
|
||||
function __construct(User $user){
|
||||
$this->user = $user;
|
||||
public function viewableFlights(User $user, ?Request $request = null)
|
||||
{
|
||||
if (Gate::denies('viewProfileData', $user)) {
|
||||
return response()->json([]);
|
||||
}
|
||||
return $this->flights($user, $request);
|
||||
}
|
||||
|
||||
public function flights(){
|
||||
return UserFlight::where('user_id', $this->user->id)
|
||||
->with([
|
||||
'departureAirport.region.country',
|
||||
'departureAirport.region.continent',
|
||||
'arrivalAirport.region.country',
|
||||
'arrivalAirport.region.continent',
|
||||
'airline.country',
|
||||
'airline.alliance',
|
||||
'aircraft',
|
||||
'seatType',
|
||||
'flightReason',
|
||||
'flightClass',
|
||||
'crewType'
|
||||
])
|
||||
->orderBy('departure_date', 'desc')
|
||||
->get();
|
||||
public function flights(User $user, ?Request $request = null)
|
||||
{
|
||||
$key = "user_flights_{$user->id}";
|
||||
|
||||
$json = Cache::remember($key, now()->addDays(30), function () use ($user) {
|
||||
return UserFlight::where('user_id', $user->id)
|
||||
->with([
|
||||
'departureAirport.region.country',
|
||||
'departureAirport.region.continent',
|
||||
'arrivalAirport.region.country',
|
||||
'arrivalAirport.region.continent',
|
||||
'airline.country',
|
||||
'airline.alliance',
|
||||
'aircraft',
|
||||
'seatType',
|
||||
'flightReason',
|
||||
'flightClass',
|
||||
'crewType'
|
||||
])
|
||||
->orderBy('departure_date', 'desc')
|
||||
->get()
|
||||
->values()
|
||||
->toJson();
|
||||
});
|
||||
|
||||
if ($request?->boolean('departed_only')) {
|
||||
$filtered = collect(json_decode($json))
|
||||
->filter(fn($f) => $f->departure_date <= now('UTC')->toDateString())
|
||||
->values()
|
||||
->toJson();
|
||||
|
||||
return response($filtered, 200)->header('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
return response($json, 200)->header('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Achievement;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Alliance;
|
||||
use App\Models\Continent;
|
||||
use App\Models\Country;
|
||||
use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use App\Http\Resources\UserFlightResource;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
if (auth()->check()) {
|
||||
$user = auth()->user();
|
||||
$defaultPage = $user->resolved_settings['default_login_page'];
|
||||
|
||||
$route = match ($defaultPage) {
|
||||
'feed_first' => $user->following()->count() > 0 ? 'feed' : 'profile.view',
|
||||
'feed' => 'feed',
|
||||
'profile' => 'profile.view',
|
||||
'dashboard' => 'dashboard',
|
||||
};
|
||||
|
||||
$args = $route == 'profile.view' ? $user->name : null;
|
||||
|
||||
return redirect()->route($route, $args);
|
||||
}
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
public static function getUserFlightApiURL(User $user){
|
||||
return '/data/user/'.$user->name.'/flights';
|
||||
}
|
||||
|
||||
public function profileData(User $user, string $view, ?int $selectedFlightId = null) : array {
|
||||
return [
|
||||
'user' => $user,
|
||||
'canView' => Gate::allows('viewProfileData', $user),
|
||||
'canEdit' => auth()->check() && (auth()->id() === $user->id || auth()->user()->hasRole('admin')),
|
||||
'initialView' => $view,
|
||||
'selectedFlightId' => $selectedFlightId,
|
||||
'flight_api_url' => self::getUserFlightApiURL($user),
|
||||
'followStatus' => auth()->check() ? auth()->user()->followStatus($user) : 'none',
|
||||
'flightCount' => $user->departedFlights()->count(),
|
||||
];
|
||||
}
|
||||
|
||||
public function departureBoard(User $user, ?UserFlight $flight = null){
|
||||
$profileData = $this->profileData($user, 'board', $flight?->id);
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function map(User $user){
|
||||
$profileData = $this->profileData($user, 'map');
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function boardingPasses(User $user){
|
||||
$profileData = $this->profileData($user, 'passes');
|
||||
return Inertia::render('UserProfile', $profileData);
|
||||
}
|
||||
|
||||
public function view(User $user, ?string $page = null)
|
||||
{
|
||||
$loggedInUser = auth()->user();
|
||||
$defaultView = $page ?: ($loggedInUser ? $loggedInUser->resolved_settings['default_profile_view'] : 'map');
|
||||
|
||||
return match($defaultView) {
|
||||
'boarding-passes' => $this->boardingPasses($user),
|
||||
'map' => $this->map($user),
|
||||
'departure-board' => $this->departureBoard($user),
|
||||
'achievements' => redirect()->route('profile.achievements', $user->name),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public function flight(User $user, UserFlight $userFlight)
|
||||
{
|
||||
if($userFlight->user_id !== $user->id){
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return Inertia::render('UserFlight', [
|
||||
'flightCount' => $user->departedFlights()->count(),
|
||||
'flight' => $userFlight->snapshot($userFlight->id),
|
||||
'canEdit' => auth()->check() && auth()->id() === $user->id,
|
||||
'canView' => Gate::allows('viewProfileData', $user),
|
||||
'user' => $user,
|
||||
'followStatus' => auth()->check() ? auth()->user()->followStatus($user) : 'none',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function achievements(User $user)
|
||||
{
|
||||
$canView = Gate::allows('viewProfileData', $user);
|
||||
|
||||
$achievements = Achievement::with(['category', 'difficulty'])
|
||||
->get()
|
||||
->groupBy(fn(Achievement $a) => $a->category->name)
|
||||
->map(fn($group) => $group->sortBy('sort_order')->values());
|
||||
|
||||
$userAchievements = $user->achievements()
|
||||
->with('achievement')
|
||||
->select(['achievement_id', 'progress'])
|
||||
->orderBy('achievement_id')
|
||||
->get()
|
||||
->keyBy('achievement_id');
|
||||
|
||||
$unlockedByCategory = $achievements->map(fn($group) =>
|
||||
$group->filter(fn($a) => $userAchievements->get($a->id)?->unlocked)->count()
|
||||
);
|
||||
|
||||
$unlockedCount = $userAchievements->filter(fn($ua) => $ua->unlocked)->count();
|
||||
return Inertia::render('UserAchievements', [
|
||||
'canView' => $canView,
|
||||
'user' => $user,
|
||||
'canEdit' => auth()->id() === $user->id,
|
||||
'followStatus' => auth()->check() ? auth()->user()->followStatus($user) : 'none',
|
||||
'achievements' => $canView ? $achievements : [],
|
||||
'userAchievements' => $canView ? $userAchievements : [],
|
||||
'loggedInUser' => auth()->user(),
|
||||
'unlockedCount' => $unlockedCount,
|
||||
'unlockedByCategory' => $unlockedByCategory,
|
||||
'totalAchievements' => $achievements->flatten()->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function achievement(User $user, Achievement $achievement)
|
||||
{
|
||||
$regions = match($achievement->internal_name){
|
||||
'fun_challenges.australian_states' => Country::whereCode('AU')->first()->sortedRegions(),
|
||||
'fun_challenges.chinese_provinces' => Country::whereCode('CN')->first()->sortedRegions(),
|
||||
'fun_challenges.canadian_provinces' => Country::whereCode('CA')->first()->sortedRegions(),
|
||||
'fun_challenges.brazilian_states' => Country::whereCode('BR')->first()->sortedRegions(),
|
||||
'fun_challenges.us_states' => Country::whereCode('US')->first()->sortedRegions(),
|
||||
default => [],
|
||||
};
|
||||
|
||||
$allianceInternalName = match($achievement->internal_name){
|
||||
'airlines_alliances.all_star_alliance' => 'star_alliance',
|
||||
'airlines_alliances.all_oneworld' => 'oneworld',
|
||||
'airlines_alliances.all_skyteam' => 'skyteam',
|
||||
'airlines_alliances.all_vanilla_alliance' => 'vanilla_alliance',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$continents = match($achievement->internal_name){
|
||||
'countries_continents.all_continent_pairs_one_way', 'countries_continents.all_continent_pairs_both_ways' => Continent::all()->toArray(),
|
||||
default => [],
|
||||
};
|
||||
|
||||
$alliance = null;
|
||||
$airlines = [];
|
||||
|
||||
if ($allianceInternalName) {
|
||||
$alliance = Alliance::where('internal_name', $allianceInternalName)
|
||||
->with('airlines')
|
||||
->firstOrFail();
|
||||
$airlines = $alliance->airlines()->with('country')->orderBy('name')->get();
|
||||
}
|
||||
|
||||
$aircraftFamilies = match($achievement->internal_name){
|
||||
'aircraft.all_boeing_7x7' => Aircraft::BOEING_FAMILIES,
|
||||
'aircraft.all_airbus_a3xx' => Aircraft::AIRBUS_FAMILIES,
|
||||
default => [],
|
||||
};
|
||||
|
||||
$canView = Gate::allows('viewProfileData', $user);
|
||||
|
||||
return Inertia::render('Profile/UserAchievement', [
|
||||
'user' => $user,
|
||||
'achievement' => $achievement,
|
||||
'loggedInUser' => auth()->user(),
|
||||
'userAchievement' => $canView ? $user->achievements()->where('achievement_id', $achievement->id)->first() : null,
|
||||
'followStatus' => auth()->check() ? auth()->user()->followStatus($user) : 'none',
|
||||
'flight_api_url' => UserProfileController::getUserFlightApiURL($user),
|
||||
'regions' => $regions,
|
||||
'alliance' => $alliance,
|
||||
'airlines' => $airlines,
|
||||
'continents' => $continents,
|
||||
'aircraft_families' => $aircraftFamilies,
|
||||
'achievementCount' => $user->unlockedAchievements()->count(),
|
||||
'canView' => $canView,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,14 @@ class HandleInertiaRequests extends Middleware
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
|
||||
return [
|
||||
...parent::share($request),
|
||||
'logo_api_url' => config('app.logo_api_url'),
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'roles' => $request->user()?->getRoleNames() ?? [],
|
||||
'permissions' => $request->user()?->getAllPermissions()->pluck('name') ?? [],
|
||||
],
|
||||
'achievement_notifications' => fn() => $request->user()
|
||||
? $request->user()
|
||||
@@ -44,6 +47,11 @@ class HandleInertiaRequests extends Middleware
|
||||
->latest()
|
||||
->get()
|
||||
: [],
|
||||
'unread_notification_count' => $request->user()?->notifications()
|
||||
->whereNull('read_at')
|
||||
->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now())
|
||||
->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
class Achievement extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'internal_name',
|
||||
@@ -38,13 +39,22 @@ class Achievement extends Model
|
||||
'achievement_category_id',
|
||||
'achievement_difficulty_id',
|
||||
'threshold',
|
||||
'has_page',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'has_page' => 'boolean',
|
||||
'progressive' => 'boolean',
|
||||
'threshold' => 'integer',
|
||||
];
|
||||
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'internal_name';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Relationships
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -15,10 +15,12 @@ class Aircraft extends Model
|
||||
'engine_type',
|
||||
'engine_count',
|
||||
'wtc',
|
||||
'preferred'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'engine_count' => 'integer',
|
||||
'preferred' => 'boolean'
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
@@ -26,6 +28,32 @@ class Aircraft extends Model
|
||||
'display_name_short'
|
||||
];
|
||||
|
||||
public const array BOEING_FAMILIES = [
|
||||
'707' => ['B701', 'B703', 'B720'],
|
||||
'717' => ['B712', 'B717'],
|
||||
'727' => ['B721', 'B722', 'B727'],
|
||||
'737' => ['B731', 'B732', 'B733', 'B734', 'B735', 'B736', 'B737', 'B738', 'B739', 'B37M', 'B38M', 'B39M'],
|
||||
'747' => ['B741', 'B742', 'B743', 'B744', 'B748', 'B74D', 'B74R', 'B74S'],
|
||||
'757' => ['B752', 'B753', 'B757'],
|
||||
'767' => ['B762', 'B763', 'B764', 'B767'],
|
||||
'777' => ['B772', 'B773', 'B77L', 'B77W', 'B778', 'B779'],
|
||||
'787' => ['B788', 'B789', 'B78X'],
|
||||
];
|
||||
|
||||
public const array AIRBUS_FAMILIES = [
|
||||
'A300' => ['A30B', 'A300', 'A306'],
|
||||
'A310' => ['A310', 'A312', 'A313'],
|
||||
'A318' => ['A318'],
|
||||
'A319' => ['A319', 'A31X'],
|
||||
'A320' => ['A320', 'A20N'],
|
||||
'A321' => ['A321', 'A21N'],
|
||||
'A330' => ['A330', 'A332', 'A333', 'A338', 'A339'],
|
||||
'A340' => ['A340', 'A342', 'A343', 'A345', 'A346'],
|
||||
'A350' => ['A350', 'A358', 'A359', 'A35K'],
|
||||
'A380' => ['A380', 'A388'],
|
||||
];
|
||||
|
||||
|
||||
protected function displayName() : Attribute{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
|
||||
@@ -12,8 +12,8 @@ class Airline extends Model
|
||||
protected $table = 'airlines';
|
||||
|
||||
protected $fillable = [
|
||||
'IATA_code',
|
||||
'ICAO_code',
|
||||
'iata_code',
|
||||
'icao_code',
|
||||
'name',
|
||||
'internal_name',
|
||||
'country_id',
|
||||
@@ -35,7 +35,7 @@ class Airline extends Model
|
||||
protected function displayName() : Attribute{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$codes = array_filter([$this->IATA_code, $this->ICAO_code]);
|
||||
$codes = array_filter([$this->iata_code, $this->icao_code]);
|
||||
$codeString = count($codes) ? ' (' . implode('/', $codes) . ')' : '';
|
||||
return "{$this->name}{$codeString}";
|
||||
}
|
||||
@@ -45,7 +45,13 @@ class Airline extends Model
|
||||
protected function logoUrl() : Attribute{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return config('app.logo_api_url') . "/airline/$this->internal_name/logo/tail";
|
||||
$user = auth()->user();
|
||||
$apiUrl = config('app.logo_api_url');
|
||||
if ($user && !$user->getSetting('ai_tail_logos')) {
|
||||
return $apiUrl .'/airline/blank/logo/tail';
|
||||
}
|
||||
|
||||
return $apiUrl . "/airline/$this->internal_name/logo/tail";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,4 +17,13 @@ class Country extends Model
|
||||
{
|
||||
return $this->hasMany(Region::class);
|
||||
}
|
||||
|
||||
function sortedRegions(): array
|
||||
{
|
||||
return $this
|
||||
->regions()
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ class Followee extends Model
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'followee_id',
|
||||
'verified',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'verified' => 'boolean',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
@@ -21,4 +26,14 @@ class Followee extends Model
|
||||
{
|
||||
return $this->belongsTo(User::class, 'followee_id');
|
||||
}
|
||||
|
||||
public function scopeVerified($query)
|
||||
{
|
||||
return $query->where('verified', true);
|
||||
}
|
||||
|
||||
public function scopePending($query)
|
||||
{
|
||||
return $query->where('verified', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class IataEquipmentCode extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'iata_code',
|
||||
'icao_code',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function aircraft(): HasMany
|
||||
{
|
||||
return $this->hasMany(Aircraft::class, 'designator', 'icao_code');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class IgnoredMissingLivery extends Model
|
||||
{
|
||||
protected $table = 'ignored_missing_liveries';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = ['filename'];
|
||||
|
||||
}
|
||||
@@ -3,58 +3,99 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Controllers\UserFlightController;
|
||||
use App\Settings\SettingsRegistry;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use App\Traits\HasAchievements;
|
||||
use App\Models\Notification;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
#[Fillable(['name', 'email', 'password'])]
|
||||
#[Fillable(['name', 'email', 'password', 'distance_unit', 'settings'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, HasAchievements, HasApiTokens;
|
||||
use HasFactory, HasAchievements, HasApiTokens, HasRoles;
|
||||
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'settings' => 'array',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
protected $appends = ['resolved_settings'];
|
||||
|
||||
public function achievements(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserAchievement::class);
|
||||
}
|
||||
|
||||
public function getSetting(string $key): mixed
|
||||
{
|
||||
$defaults = SettingsRegistry::defaults();
|
||||
return $this->settings[$key] ?? $defaults[$key] ?? null;
|
||||
}
|
||||
|
||||
public function updateSettings(array $values): void
|
||||
{
|
||||
$current = array_merge(SettingsRegistry::defaults(), $this->settings ?? []);
|
||||
$this->update(['settings' => array_merge($current, $values)]);
|
||||
}
|
||||
|
||||
function updateSetting($settingName, $value) : void{
|
||||
$this->updateSettings([$settingName => $value]);
|
||||
}
|
||||
|
||||
protected function resolvedSettings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => array_merge(SettingsRegistry::defaults(), $this->settings ?? [])
|
||||
);
|
||||
}
|
||||
|
||||
public function unlockedAchievements(): HasMany
|
||||
{
|
||||
return $this->achievements()
|
||||
->join('achievements', 'achievements.id', '=', 'user_achievements.achievement_id')
|
||||
->where(function ($query) {
|
||||
$query
|
||||
// Non-progressive achievements: always count
|
||||
->where(function ($q) {
|
||||
$q->where('achievements.progressive', false)
|
||||
->orWhereNull('achievements.progressive');
|
||||
})
|
||||
// Progressive achievements: only if progress >= threshold
|
||||
->orWhere(function ($q) {
|
||||
$q->where('achievements.progressive', true)
|
||||
->whereNotNull('achievements.threshold')
|
||||
->whereColumn('user_achievements.progress', '>=', 'achievements.threshold');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function resolveRouteBinding($value, $field = null): ?User
|
||||
{
|
||||
return $this->where('name', 'ilike', $value)->firstOrFail();
|
||||
}
|
||||
|
||||
public function FlightController(): UserFlightController
|
||||
{
|
||||
return new UserFlightController($this);
|
||||
}
|
||||
|
||||
public function flights(): HasMany {
|
||||
return $this->hasMany(UserFlight::class);
|
||||
}
|
||||
|
||||
public function departedFlights() : HasMany {
|
||||
return $this->flights()->where('departure_date', '<=', now('UTC'));
|
||||
}
|
||||
|
||||
public function upcomingFlights() : HasMany {
|
||||
return $this->flights()->where('departure_date', '>=', now('UTC'));
|
||||
}
|
||||
|
||||
public function ImportedFlights(): HasMany
|
||||
{
|
||||
return $this->hasMany(ImportedFlight::class);
|
||||
@@ -72,7 +113,21 @@ class User extends Authenticatable
|
||||
|
||||
public function isFollowing(User $user): bool
|
||||
{
|
||||
return $this->following()->where('followee_id', $user->id)->exists();
|
||||
return $this->following()
|
||||
->where('followee_id', $user->id)
|
||||
->verified()
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function followStatus(User $user): string
|
||||
{
|
||||
$followee = $this->following()->where('followee_id', $user->id)->first();
|
||||
|
||||
if (!$followee) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
return $followee->verified ? 'following' : 'requested';
|
||||
}
|
||||
|
||||
public function notifications(): HasMany
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
@@ -26,6 +27,21 @@ class UserAchievement extends Model
|
||||
'progress' => 'integer',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'unlocked',
|
||||
];
|
||||
|
||||
protected function unlocked(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (!$this->achievement) return false;
|
||||
if (!$this->achievement->progressive || !$this->achievement->threshold) return true;
|
||||
return ($this->progress ?? 0) >= $this->achievement->threshold;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Relationships
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -33,6 +33,8 @@ class UserAction extends Model
|
||||
'flight_imported' => 'Flight Imported from FR24',
|
||||
'flight_logged' => 'Flight Logged',
|
||||
'flight_deleted' => 'Flight Deleted',
|
||||
'flight_departing' => 'Flight Departed',
|
||||
'flight_arriving' => 'Flight Landed',
|
||||
default => 'Unknown Action'
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Controllers\Api\AirlineApiController;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class UserFlight extends Model
|
||||
{
|
||||
@@ -46,6 +48,9 @@ class UserFlight extends Model
|
||||
'duration_display',
|
||||
'distance',
|
||||
'livery_url',
|
||||
'scope',
|
||||
'range',
|
||||
'region_range'
|
||||
];
|
||||
|
||||
public function calculateGreatCircleDistance(): float{
|
||||
@@ -106,6 +111,26 @@ class UserFlight extends Model
|
||||
);
|
||||
}
|
||||
|
||||
protected function scope(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => $this->departureAirport->region->country_id == $this->arrivalAirport->region->country_id ? 'domestic' : 'international'
|
||||
);
|
||||
}
|
||||
protected function range(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => $this->departureAirport->region->continent_id == $this->arrivalAirport->region->continent_id ? 'intracontinental' : 'intercontinental'
|
||||
);
|
||||
}
|
||||
|
||||
protected function regionRange(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => $this->departureAirport->region_id == $this->arrivalAirport->region_id ? 'intraregional' : 'interregional'
|
||||
);
|
||||
}
|
||||
|
||||
protected function arrivalDayDifference(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -224,16 +249,37 @@ class UserFlight extends Model
|
||||
public function liveryUrl(): Attribute{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if($this->airline && $this->aircraft) {
|
||||
$fileName = "{$this->airline->internal_name}_{$this->aircraft->designator}.png";
|
||||
$file = public_path("img/liveries/generated/$fileName");
|
||||
|
||||
if (file_exists($file)) {
|
||||
return "/img/liveries/generated/$fileName";
|
||||
$user = auth()?->user();
|
||||
|
||||
$useAi = !$user || $user->getSetting('ai_liveries');
|
||||
|
||||
$apiUrl = config('app.logo_api_url');
|
||||
|
||||
if (!$this->aircraft) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if ($this->airline && $useAi){
|
||||
$path = "images/liveries/{$this->airline->internal_name}_{$this->aircraft->designator}.png";
|
||||
if (Storage::disk('local')->exists($path)) {
|
||||
$finalPath = $apiUrl."/airline/{$this->airline->internal_name}/livery/{$this->aircraft->designator}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if(empty($finalPath)){
|
||||
$path = "images/livery_templates/{$this->aircraft->designator}.png";
|
||||
if (Storage::disk('local')->exists($path)) {
|
||||
$finalPath = $apiUrl."/aircraft/{$this->aircraft->designator}/livery";
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($finalPath)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $finalPath;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class FlightObserver
|
||||
{
|
||||
protected function clearCache(UserFlight $flight): void
|
||||
{
|
||||
Cache::forget("user_flights_{$flight->user->id}");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recalculate after a flight is created.
|
||||
*/
|
||||
public function created(UserFlight $flight): void
|
||||
{
|
||||
$flight->user->calculateAchievements();
|
||||
$this->clearCache($flight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,6 +30,7 @@ class FlightObserver
|
||||
public function updated(UserFlight $flight): void
|
||||
{
|
||||
$flight->user->calculateAchievements();
|
||||
$this->clearCache($flight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,5 +40,6 @@ class FlightObserver
|
||||
public function deleted(UserFlight $flight): void
|
||||
{
|
||||
$flight->user->calculateAchievements();
|
||||
$this->clearCache($flight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class UserFlightPolicy
|
||||
*/
|
||||
public function update(User $user, UserFlight $userFlight): bool
|
||||
{
|
||||
return $user->id === $userFlight->user_id;
|
||||
return $user->id === $userFlight->user_id || $user->hasRole('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
|
||||
public function viewProfileData(?User $viewer, User $profileUser): bool
|
||||
{
|
||||
if ($viewer && ($viewer->id === $profileUser->id || $viewer->hasRole('admin'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
$isPrivate = $profileUser->resolved_settings['private_profile'] == 'private';
|
||||
|
||||
if (!$isPrivate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $viewer && $viewer->isFollowing($profileUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, User $model): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, User $model): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, User $model): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, User $model): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, User $model): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class AchievementService
|
||||
'arrivalAirport.region',
|
||||
'departureAirport.region.continent',
|
||||
'arrivalAirport.region.continent',
|
||||
])->get();
|
||||
])->where('departure_date', '<=', now('UTC'))->get();
|
||||
|
||||
foreach ($this->checkers as $checkerClass) {
|
||||
$checker = new $checkerClass($this);
|
||||
|
||||
@@ -2,37 +2,13 @@
|
||||
|
||||
namespace App\Services\Achievements\Checkers;
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\User;
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AircraftChecker extends BaseChecker
|
||||
{
|
||||
private const array BOEING_FAMILIES = [
|
||||
'707' => ['B701', 'B703', 'B720'],
|
||||
'717' => ['B712', 'B717'],
|
||||
'727' => ['B721', 'B722', 'B727'],
|
||||
'737' => ['B731', 'B732', 'B733', 'B734', 'B735', 'B736', 'B737', 'B738', 'B739', 'B37M', 'B38M', 'B39M'],
|
||||
'747' => ['B741', 'B742', 'B743', 'B744', 'B748', 'B74D', 'B74R', 'B74S'],
|
||||
'757' => ['B752', 'B753', 'B757'],
|
||||
'767' => ['B762', 'B763', 'B764', 'B767'],
|
||||
'777' => ['B772', 'B773', 'B77L', 'B77W', 'B778', 'B779'],
|
||||
'787' => ['B788', 'B789', 'B78X'],
|
||||
];
|
||||
|
||||
private const array AIRBUS_FAMILIES = [
|
||||
'A300' => ['A30B', 'A300', 'A306'],
|
||||
'A310' => ['A310', 'A312', 'A313'],
|
||||
'A318' => ['A318'],
|
||||
'A319' => ['A319', 'A31X'],
|
||||
'A320' => ['A320', 'A20N'],
|
||||
'A321' => ['A321', 'A21N'],
|
||||
'A330' => ['A330', 'A332', 'A333', 'A338', 'A339'],
|
||||
'A340' => ['A340', 'A342', 'A343', 'A345', 'A346'],
|
||||
'A350' => ['A350', 'A358', 'A359', 'A35K'],
|
||||
'A380' => ['A380', 'A388'],
|
||||
];
|
||||
|
||||
private const array DOUBLE_DECKER_DESIGNATORS = [
|
||||
// A380
|
||||
'A380', 'A388',
|
||||
@@ -121,7 +97,7 @@ class AircraftChecker extends BaseChecker
|
||||
|
||||
// --- Boeing 7x7 families ---
|
||||
|
||||
$flownBoeingFamilies = collect(self::BOEING_FAMILIES)
|
||||
$flownBoeingFamilies = collect(Aircraft::BOEING_FAMILIES)
|
||||
->filter(fn($designators) =>
|
||||
$flightsWithAircraft->contains(
|
||||
fn(UserFlight $f) => in_array($f->aircraft->designator, $designators)
|
||||
@@ -133,7 +109,7 @@ class AircraftChecker extends BaseChecker
|
||||
|
||||
// --- Airbus A3xx families ---
|
||||
|
||||
$flownAirbusFamilie = collect(self::AIRBUS_FAMILIES)
|
||||
$flownAirbusFamilie = collect(Aircraft::AIRBUS_FAMILIES)
|
||||
->filter(fn($designators) =>
|
||||
$flightsWithAircraft->contains(
|
||||
fn(UserFlight $f) => in_array($f->aircraft->designator, $designators)
|
||||
|
||||
@@ -76,6 +76,13 @@ class CountriesAndContinentsChecker extends BaseChecker
|
||||
$dep = $flight->departureAirport->region->continent->internal_name;
|
||||
$arr = $flight->arrivalAirport->region->continent->internal_name;
|
||||
|
||||
if (!in_array($dep, self::INHABITED_CONTINENTS) || !in_array($arr, self::INHABITED_CONTINENTS)) continue;
|
||||
if ($dep === $arr) {
|
||||
$depCountry = $flight->departureAirport->region->country_id;
|
||||
$arrCountry = $flight->arrivalAirport->region->country_id;
|
||||
if ($depCountry === $arrCountry) continue;
|
||||
}
|
||||
|
||||
// Directed route key e.g. "europe→asia"
|
||||
$directedRoutes->push("{$dep}→{$arr}");
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ class FunChallengesChecker extends BaseChecker
|
||||
$flights = $this->flights();
|
||||
|
||||
$airlineLetters = $flights
|
||||
->filter(fn(UserFlight $f) => $f->airline?->IATA_code !== null)
|
||||
->map(fn(UserFlight $f) => strtoupper($f->airline->IATA_code[0]))
|
||||
->filter(fn(UserFlight $f) => $f->airline?->iata_code !== null)
|
||||
->map(fn(UserFlight $f) => strtoupper($f->airline->iata_code[0]))
|
||||
->filter(fn($letter) => ctype_alpha($letter))
|
||||
->unique()
|
||||
->count();
|
||||
|
||||
@@ -70,6 +70,12 @@ class GeneralFlyingChecker extends BaseChecker
|
||||
|
||||
// --- Progressive achievements ---
|
||||
|
||||
$totalDistance = $flights->sum('distance');
|
||||
|
||||
$this->awardProgress((int) $totalDistance, 'general_flying.circumference_of_the_earth');
|
||||
$this->awardProgress((int) $totalDistance, 'general_flying.to_the_moon');
|
||||
$this->awardProgress((int) $totalDistance, 'general_flying.gigametre');
|
||||
|
||||
$this->awardProgress($count,'general_flying.10_flights');
|
||||
$this->awardProgress($count,'general_flying.50_flights');
|
||||
$this->awardProgress($count,'general_flying.100_flights');
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\DTOs\MissingLivery;
|
||||
use App\Models\IgnoredMissingLivery;
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AdminService
|
||||
{
|
||||
/** @return Collection<int, MissingLivery> */
|
||||
function getMissingLiveries(): Collection
|
||||
{
|
||||
|
||||
/* $existingFiles = collect(glob(public_path('img/liveries/generated/*')))
|
||||
->map(fn ($path) => pathinfo($path, PATHINFO_FILENAME))
|
||||
->toArray();*/
|
||||
|
||||
|
||||
$existingFiles = collect(glob(Storage::disk('local')->path('images/liveries').'/*.png'))
|
||||
->map(fn ($path) => pathinfo($path, PATHINFO_FILENAME))
|
||||
->toArray();
|
||||
|
||||
$combos = UserFlight::with(['aircraft', 'airline'])
|
||||
->select('airline_id', 'aircraft_id')
|
||||
->whereNotNull('airline_id')
|
||||
->whereNotNull('aircraft_id')
|
||||
->distinct()
|
||||
->get()
|
||||
->filter(fn ($flight) => $flight->aircraft && $flight->airline)
|
||||
->map(fn ($flight) => [
|
||||
'airline_name' => $flight->airline->name,
|
||||
'aircraft_display_name' => $flight->aircraft->display_name,
|
||||
'filename' => $flight->airline->internal_name . '_' . $flight->aircraft->designator,
|
||||
'clipboard_text' => $flight->airline->name . ' ' . $flight->aircraft->display_name_short,
|
||||
])
|
||||
->filter(fn ($combo) => !in_array($combo['filename'], $existingFiles));
|
||||
|
||||
$ignoredFiles = IgnoredMissingLivery::whereIn('filename', $combos->pluck('filename'))->pluck('filename')->toArray();
|
||||
|
||||
return $combos
|
||||
->filter(fn ($combo) => !in_array($combo['filename'], $ignoredFiles))
|
||||
->sortBy('airline_name')
|
||||
->values();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\DTOs\FlightStatData;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\IataEquipmentCode;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class FlightStatsService
|
||||
{
|
||||
|
||||
public function fetchOtherDays(string $airlineCode, string $flightNumber): array
|
||||
{
|
||||
$url = sprintf(
|
||||
'https://www.flightstats.com/v2/api-next/flight-tracker/other-days/%s/%s',
|
||||
$airlineCode,
|
||||
$flightNumber,
|
||||
);
|
||||
|
||||
$response = Http::withOptions([
|
||||
'verify' => config('app.verify_ssl'),
|
||||
])->get($url);
|
||||
|
||||
if (!$response->successful()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = $response->json('data');
|
||||
|
||||
if (!is_array($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function fetchFlightData(string $airlineCode, string $flightNumber, ?CarbonImmutable $date = null): ?FlightStatData
|
||||
{
|
||||
$specificDate = $date !== null;
|
||||
$date ??= now()->utc()->toImmutable();
|
||||
|
||||
$data = $this->fetchForDate($airlineCode, $flightNumber, $date);
|
||||
|
||||
if ($data || $specificDate) return $data;
|
||||
|
||||
$otherDays = $this->fetchOtherDays($airlineCode, $flightNumber);
|
||||
|
||||
$pastDays = collect($otherDays)
|
||||
->filter(fn($day) => !empty($day['flights']))
|
||||
->sortByDesc(fn($day) => $day['year'] . $day['date1']);
|
||||
|
||||
foreach ($pastDays as $day) {
|
||||
$pastDate = CarbonImmutable::createFromFormat('Y-d-M', $day['year'] . '-' . $day['date1']);
|
||||
$result = $this->fetchForDate($airlineCode, $flightNumber, $pastDate);
|
||||
if ($result) return $result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function fetchForDate(string $airlineCode, string $flightNumber, CarbonImmutable $date): ?FlightStatData
|
||||
{
|
||||
$url = sprintf(
|
||||
'https://www.flightstats.com/v2/api-next/flight-tracker/%s/%s/%d/%d/%d',
|
||||
$airlineCode,
|
||||
$flightNumber,
|
||||
$date->year,
|
||||
$date->month,
|
||||
$date->day,
|
||||
);
|
||||
|
||||
$response = Http::withOptions([
|
||||
'verify' => config('app.verify_ssl'),
|
||||
])->get($url);
|
||||
|
||||
if (!$response->successful()) {
|
||||
Log::warning("FlightStats request failed for {$airlineCode}{$flightNumber}: HTTP {$response->status()}");
|
||||
return null;
|
||||
}
|
||||
|
||||
$flightData = $response->json('data');
|
||||
|
||||
if (empty($flightData)) return null;
|
||||
|
||||
return FlightStatData::fromApiResponse($flightData);
|
||||
}
|
||||
|
||||
public function guessAircraftFromIata(string $iataCode): ?Aircraft
|
||||
{
|
||||
$equipment = IataEquipmentCode::where('iata_code', $iataCode)->first();
|
||||
|
||||
if (!$equipment) {
|
||||
Log::info("Unknown IATA equipment code: {$iataCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
$aircraft = Aircraft::where('designator', $equipment->icao_code)
|
||||
->orderByDesc('preferred')
|
||||
->orderBy('id')
|
||||
->first();
|
||||
|
||||
if (!$aircraft) {
|
||||
Log::info("No aircraft found for ICAO: {$equipment->icao_code} (IATA: {$iataCode})");
|
||||
}
|
||||
|
||||
return $aircraft;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Settings;
|
||||
|
||||
class SettingsRegistry
|
||||
{
|
||||
|
||||
public static function categories(): array
|
||||
{
|
||||
return [
|
||||
'Units of Measurement' => 'Select either metric or incorrect units.',
|
||||
'FlightsGoneBy Settings' => 'Settings for Site Behaviour',
|
||||
'AI Generated Content' => 'Airline tail logos and liveries are AI generated with human cleanup. If you would rather not see any AI, then our blank aircraft templates are human created.',
|
||||
'Account & Privacy' => 'Everything to do with your account.',
|
||||
];
|
||||
}
|
||||
|
||||
public static function schema(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'key' => 'distance_unit',
|
||||
'type' => 'select',
|
||||
'label' => 'Distance Units',
|
||||
'category' => 'Units of Measurement',
|
||||
'default' => 'km',
|
||||
'options' => [
|
||||
['value' => 'km', 'label' => 'Kilometres (km)'],
|
||||
['value' => 'mi', 'label' => 'Miles (mi)'],
|
||||
['value' => 'nm', 'label' => 'Nautical miles (nm)'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Account & Privacy',
|
||||
'key' => 'private_profile',
|
||||
'type' => 'select',
|
||||
'label' => 'Account Privacy',
|
||||
'default' => 'public',
|
||||
'options' => [
|
||||
['value' => 'public', 'label' => 'Public Profile Viewable By Everyone'],
|
||||
['value' => 'private', 'label' => 'Private Profile Viewable Only By You and Your Approved Followers'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'default_login_page',
|
||||
'type' => 'select',
|
||||
'label' => 'Default Page',
|
||||
'category' => 'FlightsGoneBy Settings',
|
||||
'default' => 'feed_first',
|
||||
'options' => [
|
||||
['value' => 'feed_first', 'label' => 'My Feed if Following People, My Profile if Not'],
|
||||
['value' => 'profile', 'label' => 'My Profile'],
|
||||
['value' => 'feed', 'label' => 'My Feed'],
|
||||
['value' => 'dashboard', 'label' => 'My Dashboard'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'default_profile_view',
|
||||
'type' => 'select',
|
||||
'label' => 'Default View When Loading a Profile',
|
||||
'category' => 'FlightsGoneBy Settings',
|
||||
'default' => 'departure-board',
|
||||
'options' => [
|
||||
['value' => 'departure-board', 'label' => 'Departure Board'],
|
||||
['value' => 'map', 'label' => 'Map'],
|
||||
['value' => 'boarding-passes', 'label' => 'Boarding Passes'],
|
||||
['value' => 'achievements', 'label' => 'Achievements'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'show_map_legend',
|
||||
'type' => 'checkbox',
|
||||
'label' => 'Expand Map Legend By Default',
|
||||
'category' => 'FlightsGoneBy Settings',
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'key' => 'hide_impossible_achievements',
|
||||
'type' => 'checkbox',
|
||||
'label' => 'Hide Impossible Achievements By Default',
|
||||
'category' => 'FlightsGoneBy Settings',
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'category' => 'AI Generated Content',
|
||||
'key' => 'ai_liveries',
|
||||
'type' => 'checkbox',
|
||||
'label' => 'Show AI Generated Livery Images',
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'category' => 'AI Generated Content',
|
||||
'key' => 'ai_tail_logos',
|
||||
'type' => 'checkbox',
|
||||
'label' => 'Show AI Generated Tail Logos',
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'key' => 'departure_board_columns',
|
||||
'category' => 'FlightsGoneBy Settings',
|
||||
'type' => 'multiselect',
|
||||
'label' => 'Which columns to show on the Departure Board',
|
||||
'default' => ['airline', 'flight_number', 'from', 'to', 'departure_date', 'departure_time', 'arrival_time', 'duration', 'distance', 'aircraft', 'registration', 'class_seat_combined'],
|
||||
'options' => [
|
||||
['value' => 'airline', 'label' => 'Airline'],
|
||||
['value' => 'flight_number', 'label' => 'Flight Number'],
|
||||
['value' => 'from', 'label' => 'From'],
|
||||
['value' => 'to', 'label' => 'To'],
|
||||
['value' => 'departure_date', 'label' => 'Departure Date'],
|
||||
['value' => 'departure_time', 'label' => 'Departure Time'],
|
||||
['value' => 'arrival_time', 'label' => 'Arrival Time'],
|
||||
['value' => 'duration', 'label' => 'Duration'],
|
||||
['value' => 'distance', 'label' => 'Distance'],
|
||||
['value' => 'aircraft', 'label' => 'Aircraft'],
|
||||
['value' => 'registration', 'label' => 'Aircraft Registration'],
|
||||
['value' => 'class_seat_combined', 'label' => 'Class/Seat Type/Seat Number Combined'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function defaults(): array
|
||||
{
|
||||
return collect(static::schema())
|
||||
->pluck('default', 'key')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function validationRules(): array
|
||||
{
|
||||
$rules = [];
|
||||
foreach (static::schema() as $field) {
|
||||
$key = "settings.{$field['key']}";
|
||||
$rules[$key] = match ($field['type']) {
|
||||
'select' => ['required', 'string', 'in:' . implode(',', array_column($field['options'], 'value'))],
|
||||
'checkbox' => ['boolean'],
|
||||
'text' => ['nullable', 'string', 'max:255'],
|
||||
'multiselect' => ['nullable', 'array'],
|
||||
"settings.{$field['key']}.*" => ['string'],
|
||||
default => ['nullable'],
|
||||
};
|
||||
}
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ trait HasAchievements
|
||||
{
|
||||
public function calculateAchievements(): void
|
||||
{
|
||||
/** @var User $this */
|
||||
app(AchievementService::class)->calculate($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\HandleInertiaRequests;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Spatie\Permission\Middleware\PermissionMiddleware;
|
||||
use Spatie\Permission\Middleware\RoleMiddleware;
|
||||
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
|
||||
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
@@ -13,12 +22,71 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
$middleware->web(append: [
|
||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
|
||||
HandleInertiaRequests::class,
|
||||
AddLinkHeadersForPreloadedAssets::class,
|
||||
]);
|
||||
$middleware->alias([
|
||||
'role' => RoleMiddleware::class,
|
||||
'permission' => PermissionMiddleware::class,
|
||||
'role_or_permission' => RoleOrPermissionMiddleware::class,
|
||||
]);
|
||||
|
||||
//
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
$exceptions->respond(function ($response, Throwable $e, Request $request) {
|
||||
$status = $response->getStatusCode();
|
||||
|
||||
$errors = [
|
||||
403 => [
|
||||
'title' => "The Cockpit is Off Limits",
|
||||
'message' => 'You don\'t have permission to access this page.',
|
||||
],
|
||||
404 => [
|
||||
'title' => 'You Flight Has Been Cancelled',
|
||||
'message' => 'The page you are looking for doesn\'t exist or has been moved.',
|
||||
],
|
||||
419 => [
|
||||
'title' => 'Page Expired',
|
||||
'message' => 'Your session has expired. Please refresh the page and try again.',
|
||||
],
|
||||
429 => [
|
||||
'title' => 'Too Many Requests',
|
||||
'message' => 'You\'re making too many requests. Please slow down and try again.',
|
||||
],
|
||||
500 => [
|
||||
'title' => 'This Plane Has Made An Emergency Landing',
|
||||
'message' => 'Something went wrong on our end. Please try again later.',
|
||||
],
|
||||
503 => [
|
||||
'title' => 'Service Unavailable',
|
||||
'message' => 'We\'re down for maintenance. Please check back soon.',
|
||||
],
|
||||
];
|
||||
|
||||
$isLocal = app()->environment(['local', 'testing']);
|
||||
$handled = array_keys($errors);
|
||||
$friendlyErrorsOnLocal = [404, 403];
|
||||
|
||||
// In local/testing, only handle 404. In production, handle all.
|
||||
$shouldHandle = isset($errors[$status]) && (
|
||||
!$isLocal || in_array($status, $friendlyErrorsOnLocal)
|
||||
);
|
||||
|
||||
if (!$shouldHandle) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return Inertia::render('Error', [
|
||||
'statusCode' => $status,
|
||||
'statusTitle' => $errors[$status]['title'],
|
||||
'statusMessage' => $errors[$status]['message'],
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'roles' => $request->user()?->getRoleNames() ?? [],
|
||||
'permissions' => $request->user()?->getAllPermissions()->pluck('name') ?? [],
|
||||
],
|
||||
])
|
||||
->toResponse($request)
|
||||
->setStatusCode($status);
|
||||
});
|
||||
})->create();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^3.0",
|
||||
"spatie/laravel-permission": "^8.0",
|
||||
"tightenco/ziggy": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0e560320885031dd36bb08bb44fe05d4",
|
||||
"content-hash": "2fab3a0703fff56cedb9d4c9b650e2fc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -3433,6 +3433,154 @@
|
||||
},
|
||||
"time": "2025-12-14T04:43:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-package-tools",
|
||||
"version": "1.93.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-package-tools.git",
|
||||
"reference": "d5552849801f2642aea710557463234b59ef65eb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb",
|
||||
"reference": "d5552849801f2642aea710557463234b59ef65eb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.5",
|
||||
"orchestra/testbench": "^8.0|^9.2|^10.0|^11.0",
|
||||
"pestphp/pest": "^2.1|^3.1|^4.0",
|
||||
"phpunit/php-code-coverage": "^10.0|^11.0|^12.0",
|
||||
"phpunit/phpunit": "^10.5|^11.5|^12.5",
|
||||
"spatie/pest-plugin-test-time": "^2.2|^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\LaravelPackageTools\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Tools for creating Laravel packages",
|
||||
"homepage": "https://github.com/spatie/laravel-package-tools",
|
||||
"keywords": [
|
||||
"laravel-package-tools",
|
||||
"spatie"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-package-tools/issues",
|
||||
"source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-19T14:06:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-permission",
|
||||
"version": "8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-permission.git",
|
||||
"reference": "70a6ab04108616b438e0839598f473b513281644"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/70a6ab04108616b438e0839598f473b513281644",
|
||||
"reference": "70a6ab04108616b438e0839598f473b513281644",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/auth": "^12.0|^13.0",
|
||||
"illuminate/container": "^12.0|^13.0",
|
||||
"illuminate/contracts": "^12.0|^13.0",
|
||||
"illuminate/database": "^12.0|^13.0",
|
||||
"php": "^8.3",
|
||||
"spatie/laravel-package-tools": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"larastan/larastan": "^3.9",
|
||||
"laravel/passport": "^13.0",
|
||||
"laravel/pint": "^1.0",
|
||||
"orchestra/testbench": "^10.0|^11.0",
|
||||
"pestphp/pest": "^3.0|^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^3.0|^4.1",
|
||||
"phpstan/phpstan": "^2.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\Permission\\PermissionServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "8.x-dev",
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Spatie\\Permission\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Permission handling for Laravel 12 and up",
|
||||
"homepage": "https://github.com/spatie/laravel-permission",
|
||||
"keywords": [
|
||||
"acl",
|
||||
"laravel",
|
||||
"permission",
|
||||
"permissions",
|
||||
"rbac",
|
||||
"roles",
|
||||
"security",
|
||||
"spatie"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-permission/issues",
|
||||
"source": "https://github.com/spatie/laravel-permission/tree/8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/spatie",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-30T19:30:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v8.0.0",
|
||||
|
||||
@@ -40,6 +40,7 @@ return [
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
'verify_ssl' => env('VERIFY_SSL', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -32,7 +32,7 @@ return [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'root' => env('LOCAL_DISK_ROOT', storage_path('app/private')),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
use Spatie\Permission\DefaultTeamResolver;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||
* is often just the "Permission" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Permission model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Permission` contract.
|
||||
*/
|
||||
|
||||
'permission' => Permission::class,
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||
* is often just the "Role" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Role model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Role` contract.
|
||||
*/
|
||||
|
||||
'role' => Role::class,
|
||||
|
||||
/*
|
||||
* When using the "Teams" feature from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your teams. Of course, it
|
||||
* is often just the "Team" model but you may use whatever you like.
|
||||
*/
|
||||
'team' => null,
|
||||
|
||||
/*
|
||||
* When using the "HasModels" trait and passing raw IDs to syncModels,
|
||||
* attachModels, or detachModels, this model class will be used to
|
||||
* resolve those IDs. If null, defaults to the guard's model.
|
||||
*/
|
||||
'default_model' => null,
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'roles' => 'roles',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your permissions. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'permissions' => 'permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_permissions' => 'model_has_permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models roles. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_roles' => 'model_has_roles',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'role_has_permissions' => 'role_has_permissions',
|
||||
],
|
||||
|
||||
'column_names' => [
|
||||
/*
|
||||
* Change this if you want to name the related pivots other than defaults
|
||||
*/
|
||||
'role_pivot_key' => null, // default 'role_id',
|
||||
'permission_pivot_key' => null, // default 'permission_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to name the related model primary key other than
|
||||
* `model_id`.
|
||||
*
|
||||
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||
* that case, name this `model_uuid`.
|
||||
*/
|
||||
|
||||
'model_morph_key' => 'model_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to use the teams feature and your related model's
|
||||
* foreign key is other than `team_id`.
|
||||
*/
|
||||
|
||||
'team_foreign_key' => 'team_id',
|
||||
],
|
||||
|
||||
/*
|
||||
* When set to true, the method for checking permissions will be registered on the gate.
|
||||
* Set this to false if you want to implement custom logic for checking permissions.
|
||||
*/
|
||||
|
||||
'register_permission_check_method' => true,
|
||||
|
||||
/*
|
||||
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||
*/
|
||||
'register_octane_reset_listener' => false,
|
||||
|
||||
/*
|
||||
* Events will fire when a role or permission is assigned/unassigned:
|
||||
* \Spatie\Permission\Events\RoleAttachedEvent
|
||||
* \Spatie\Permission\Events\RoleDetachedEvent
|
||||
* \Spatie\Permission\Events\PermissionAttachedEvent
|
||||
* \Spatie\Permission\Events\PermissionDetachedEvent
|
||||
*
|
||||
* To enable, set to true, and then create listeners to watch these events.
|
||||
*/
|
||||
'events_enabled' => false,
|
||||
|
||||
/*
|
||||
* Teams Feature.
|
||||
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||
* If you want the migrations to register the 'team_foreign_key', you must
|
||||
* set this to true before doing the migration.
|
||||
* If you already did the migration then you must make a new migration to also
|
||||
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||
* (view the latest version of this package's migration file)
|
||||
*/
|
||||
|
||||
'teams' => false,
|
||||
|
||||
/*
|
||||
* The class to use to resolve the permissions team id
|
||||
*/
|
||||
'team_resolver' => DefaultTeamResolver::class,
|
||||
|
||||
/*
|
||||
* Passport Client Credentials Grant
|
||||
* When set to true the package will use Passports Client to check permissions
|
||||
*/
|
||||
|
||||
'use_passport_client_credentials' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required permission names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_permission_in_exception' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required role names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_role_in_exception' => false,
|
||||
|
||||
/*
|
||||
* By default wildcard permission lookups are disabled.
|
||||
* See documentation to understand supported syntax.
|
||||
*/
|
||||
|
||||
'enable_wildcard_permission' => false,
|
||||
|
||||
/*
|
||||
* The class to use for interpreting wildcard permissions.
|
||||
* If you need to modify delimiters, override the class and specify its name here.
|
||||
*/
|
||||
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||
|
||||
/* Cache-specific settings */
|
||||
|
||||
'cache' => [
|
||||
|
||||
/*
|
||||
* By default all permissions are cached for 24 hours to speed up performance.
|
||||
* When permissions or roles are updated the cache is flushed automatically.
|
||||
*/
|
||||
|
||||
'expiration_time' => DateInterval::createFromDateString('24 hours'),
|
||||
|
||||
/*
|
||||
* The cache key used to store all permissions.
|
||||
*/
|
||||
|
||||
'key' => 'spatie.permission.cache',
|
||||
|
||||
/*
|
||||
* You may optionally indicate a specific cache driver to use for permission and
|
||||
* role caching using any of the `store` drivers listed in the cache.php config
|
||||
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||
*/
|
||||
|
||||
'store' => 'default',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Achievement;
|
||||
use App\Models\Aircraft;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected array $aircraft_codes = [
|
||||
'100' => ['icao' => 'F100', 'description' => 'Fokker 100', 'wtc' => 'M'],
|
||||
'141' => ['icao' => 'B461', 'description' => 'BAe 146-100 Pax', 'wtc' => 'M'],
|
||||
'142' => ['icao' => 'B462', 'description' => 'BAe 146-200 Pax', 'wtc' => 'M'],
|
||||
'143' => ['icao' => 'B463', 'description' => 'BAe 146-300 Pax', 'wtc' => 'M'],
|
||||
'146' => ['icao' => '', 'description' => 'BAe 146 all pax models', 'wtc' => 'M'],
|
||||
'14F' => ['icao' => '', 'description' => 'BAe 146 Freighter (-100/200/300QT & QC)', 'wtc' => 'M'],
|
||||
'14X' => ['icao' => 'B461', 'description' => 'BAe 146 Freighter (-100QT & QC)', 'wtc' => 'M'],
|
||||
'14Y' => ['icao' => 'B462', 'description' => 'BAe 146 Freighter (-200QT & QC)', 'wtc' => 'M'],
|
||||
'14Z' => ['icao' => 'B463', 'description' => 'BAe 146 Freighter (-200QT & QC)', 'wtc' => 'M'],
|
||||
'290' => ['icao' => 'E290', 'description' => 'Embraer E190-E2', 'wtc' => 'M'],
|
||||
'295' => ['icao' => 'E295', 'description' => 'Embraer E195-E2', 'wtc' => 'M'],
|
||||
'221' => ['icao' => 'BCS1', 'description' => 'Airbus A220-100', 'wtc' => 'M'],
|
||||
'223' => ['icao' => 'BCS3', 'description' => 'Airbus A220-200', 'wtc' => 'M'],
|
||||
'310' => ['icao' => 'A310', 'description' => 'Airbus A310 all pax models', 'wtc' => 'H'],
|
||||
'312' => ['icao' => 'A310', 'description' => 'Airbus A310-200 pax', 'wtc' => 'H'],
|
||||
'313' => ['icao' => 'A310', 'description' => 'Airbus A310-300 pax', 'wtc' => 'H'],
|
||||
'318' => ['icao' => 'A318', 'description' => 'Airbus A318', 'wtc' => 'M'],
|
||||
'319' => ['icao' => 'A319', 'description' => 'Airbus A319 Ceo', 'wtc' => 'M'],
|
||||
'31F' => ['icao' => 'A310', 'description' => 'Airbus A310 Freighter', 'wtc' => 'M'],
|
||||
'31N' => ['icao' => 'A19N', 'description' => 'Airbus A319 Neo', 'wtc' => 'M'],
|
||||
'31X' => ['icao' => 'A310', 'description' => 'Airbus A310-200 Freighter', 'wtc' => 'M'],
|
||||
'31Y' => ['icao' => 'A310', 'description' => 'Airbus A310-300 Freighter', 'wtc' => 'M'],
|
||||
'320' => ['icao' => 'A320', 'description' => 'Airbus A320-100/200 Ceo', 'wtc' => 'M'],
|
||||
'321' => ['icao' => 'A321', 'description' => 'Airbus A321-100/200 Ceo', 'wtc' => 'M'],
|
||||
'32A' => ['icao' => 'A320', 'description' => 'Airbus A320-200 Ceo (Sharklets)', 'wtc' => 'M'],
|
||||
'32C' => ['icao' => 'A318', 'description' => 'Airbus A318 (Sharklets)', 'wtc' => 'M'],
|
||||
'32D' => ['icao' => 'A319', 'description' => 'Airbus A319 Ceo (Sharklets)', 'wtc' => 'M'],
|
||||
'32N' => ['icao' => 'A20N', 'description' => 'Airbus A320-200 Neo', 'wtc' => 'M'],
|
||||
'32Q' => ['icao' => 'A21N', 'description' => 'Airbus A321-200 Neo', 'wtc' => 'M'],
|
||||
'32S' => ['icao' => 'n/a', 'description' => 'Airbus A318/319/320/321', 'wtc' => 'M'],
|
||||
'330' => ['icao' => 'A330', 'description' => 'Airbus A330 all models', 'wtc' => 'H'],
|
||||
'332' => ['icao' => 'A332', 'description' => 'Airbus A330-200', 'wtc' => 'H'],
|
||||
'333' => ['icao' => 'A333', 'description' => 'Airbus A330-300', 'wtc' => 'H'],
|
||||
'338' => ['icao' => 'A338', 'description' => 'Airbus A330-800 Neo', 'wtc' => 'H'],
|
||||
'339' => ['icao' => 'A339', 'description' => 'Airbus A330-900 Neo', 'wtc' => 'H'],
|
||||
'33X' => ['icao' => 'A332', 'description' => 'Airbus A330-200 Freighter', 'wtc' => 'H'],
|
||||
'340' => ['icao' => 'A340', 'description' => 'Airbus A340 all models', 'wtc' => 'H'],
|
||||
'342' => ['icao' => 'A342', 'description' => 'Airbus A340-200', 'wtc' => 'H'],
|
||||
'343' => ['icao' => 'A343', 'description' => 'Airbus A340-300', 'wtc' => 'H'],
|
||||
'345' => ['icao' => 'A345', 'description' => 'Airbus A340-500', 'wtc' => 'H'],
|
||||
'346' => ['icao' => 'A346', 'description' => 'Airbus A340-600', 'wtc' => 'H'],
|
||||
'351' => ['icao' => 'A35K', 'description' => 'Airbus A350-1000', 'wtc' => 'H'],
|
||||
'359' => ['icao' => 'A359', 'description' => 'Airbus A350-900', 'wtc' => 'H'],
|
||||
'380' => ['icao' => 'A388', 'description' => 'Airbus A380 pax', 'wtc' => 'J'],
|
||||
'38F' => ['icao' => '', 'description' => 'Airbus A380 Freighter', 'wtc' => 'J'],
|
||||
'703' => ['icao' => 'B703', 'description' => 'Boeing 707-300 pax', 'wtc' => 'H'],
|
||||
'707' => ['icao' => 'n/a', 'description' => 'Boeing 707/720 all pax models', 'wtc' => 'H'],
|
||||
'70F' => ['icao' => 'B703', 'description' => 'Boeing 707 Freighter', 'wtc' => 'H'],
|
||||
'70M' => ['icao' => 'B703', 'description' => 'Boeing 707 Combi', 'wtc' => 'H'],
|
||||
'717' => ['icao' => 'B712', 'description' => 'Boeing 717', 'wtc' => 'M'],
|
||||
'721' => ['icao' => 'B721', 'description' => 'Boeing 727-100 pax', 'wtc' => 'M'],
|
||||
'722' => ['icao' => 'B722', 'description' => 'Boeing 727-200 pax', 'wtc' => 'M'],
|
||||
'727' => ['icao' => 'n/a', 'description' => 'Boeing 727 all pax models', 'wtc' => 'M'],
|
||||
'72B' => ['icao' => 'B721', 'description' => 'Boeing 727-100 Mixed Configuration', 'wtc' => 'M'],
|
||||
'72C' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Mixed Configuration', 'wtc' => 'M'],
|
||||
'72F' => ['icao' => 'n/a', 'description' => 'Boeing 727 Freighter (-100/200)', 'wtc' => 'M'],
|
||||
'72M' => ['icao' => 'n/a', 'description' => 'Boeing 727 Combi', 'wtc' => 'M'],
|
||||
'72S' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Advanced pax', 'wtc' => 'M'],
|
||||
'72W' => ['icao' => 'B721', 'description' => 'Boeing 727-200 (winglets) pax', 'wtc' => 'M'],
|
||||
'72X' => ['icao' => 'B721', 'description' => 'Boeing 727-100 Freighter', 'wtc' => 'M'],
|
||||
'72Y' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Freighter', 'wtc' => 'M'],
|
||||
'731' => ['icao' => 'B731', 'description' => 'Boeing 737-100 pax', 'wtc' => 'M'],
|
||||
'732' => ['icao' => 'B732', 'description' => 'Boeing 737-200 pax', 'wtc' => 'M'],
|
||||
'733' => ['icao' => 'B733', 'description' => 'Boeing 737-300 pax', 'wtc' => 'M'],
|
||||
'734' => ['icao' => 'B734', 'description' => 'Boeing 737-400 pax', 'wtc' => 'M'],
|
||||
'735' => ['icao' => 'B735', 'description' => 'Boeing 737-500 pax', 'wtc' => 'M'],
|
||||
'736' => ['icao' => 'B736', 'description' => 'Boeing 737-600 pax', 'wtc' => 'M'],
|
||||
'737' => ['icao' => 'n/a', 'description' => 'Boeing 737 all pax models', 'wtc' => 'M'],
|
||||
'738' => ['icao' => 'B738', 'description' => 'Boeing 737-800 pax', 'wtc' => 'M'],
|
||||
'739' => ['icao' => 'B739', 'description' => 'Boeing 737-900 pax', 'wtc' => 'M'],
|
||||
'73C' => ['icao' => 'B733', 'description' => 'Boeing 737-300 (winglets) pax', 'wtc' => 'M'],
|
||||
'73E' => ['icao' => 'B735', 'description' => 'Boeing 737-500 (winglets) pax', 'wtc' => 'M'],
|
||||
'73F' => ['icao' => 'n/a', 'description' => 'Boeing 737 all Freighter models', 'wtc' => 'M'],
|
||||
'73G' => ['icao' => 'B737', 'description' => 'Boeing 737-700 pax', 'wtc' => 'M'],
|
||||
'73H' => ['icao' => 'B738', 'description' => 'Boeing 737-800 (winglets) pax', 'wtc' => 'M'],
|
||||
'73J' => ['icao' => 'B739', 'description' => 'Boeing 737-900 (winglets) pax', 'wtc' => 'M'],
|
||||
'73L' => ['icao' => 'B732', 'description' => 'Boeing 737-200 Combi', 'wtc' => 'M'],
|
||||
'73M' => ['icao' => 'n/a', 'description' => 'Boeing 737 Combi', 'wtc' => 'M'],
|
||||
'73P' => ['icao' => 'B734', 'description' => 'Boeing 737-400 Freighter', 'wtc' => 'M'],
|
||||
'73Q' => ['icao' => 'B734', 'description' => 'Boeing 737-400 Combi', 'wtc' => 'M'],
|
||||
'73R' => ['icao' => 'B737', 'description' => 'Boeing 737-700 Combi', 'wtc' => 'M'],
|
||||
'73W' => ['icao' => 'B737', 'description' => 'Boeing 737-700 (winglets) pax', 'wtc' => 'M'],
|
||||
'73X' => ['icao' => 'B732', 'description' => 'Boeing 737-200 Freighter', 'wtc' => 'M'],
|
||||
'73Y' => ['icao' => 'B733', 'description' => 'Boeing 737-300 Freighter', 'wtc' => 'M'],
|
||||
'741' => ['icao' => 'B741', 'description' => 'Boeing 747-100 pax', 'wtc' => 'H'],
|
||||
'742' => ['icao' => 'B742', 'description' => 'Boeing 747-200 pax', 'wtc' => 'H'],
|
||||
'743' => ['icao' => 'B743', 'description' => 'Boeing 747-300 pax', 'wtc' => 'H'],
|
||||
'744' => ['icao' => 'B744', 'description' => 'Boeing 747-400 pax', 'wtc' => 'H'],
|
||||
'747' => ['icao' => 'n/a', 'description' => 'Boeing 747 all pax models', 'wtc' => 'H'],
|
||||
'748' => ['icao' => 'B748', 'description' => 'Boeing 747-8 pax', 'wtc' => 'H'],
|
||||
'74B' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Swingtail Freighter', 'wtc' => 'H'],
|
||||
'74C' => ['icao' => 'B742', 'description' => 'Boeing 747-200 Combi', 'wtc' => 'H'],
|
||||
'74D' => ['icao' => 'B743', 'description' => 'Boeing 747-300 Combi', 'wtc' => 'H'],
|
||||
'74E' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Combi', 'wtc' => 'H'],
|
||||
'74F' => ['icao' => 'n/a', 'description' => 'Boeing 747 all Freighter models', 'wtc' => 'H'],
|
||||
'74H' => ['icao' => 'n/a', 'description' => 'Boeing 747-8I Passenger', 'wtc' => 'H'],
|
||||
'74J' => ['icao' => 'B744', 'description' => 'Boeing 747-400 (Domestic) pax', 'wtc' => 'H'],
|
||||
'74L' => ['icao' => 'B74S', 'description' => 'Boeing 747SP', 'wtc' => 'H'],
|
||||
'74M' => ['icao' => 'n/a', 'description' => 'Boeing 747 all Combi models', 'wtc' => 'H'],
|
||||
'74N' => ['icao' => 'n/a', 'description' => 'Boeing 747-8F', 'wtc' => 'H'],
|
||||
'74R' => ['icao' => 'B74R', 'description' => 'Boeing 747SR pax', 'wtc' => 'H'],
|
||||
'74T' => ['icao' => 'B741', 'description' => 'Boeing 747-100 Freighter', 'wtc' => 'H'],
|
||||
'74U' => ['icao' => 'B743', 'description' => 'Boeing 747-300 / 747-200 SUD Freighter', 'wtc' => 'H'],
|
||||
'74V' => ['icao' => 'B74R', 'description' => 'Boeing 747SR Freighter', 'wtc' => 'H'],
|
||||
'74X' => ['icao' => 'B742', 'description' => 'Boeing 747-200 Freighter', 'wtc' => 'H'],
|
||||
'74Y' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Freighter', 'wtc' => 'H'],
|
||||
'752' => ['icao' => 'B752', 'description' => 'Boeing 757-200 pax', 'wtc' => 'H'],
|
||||
'753' => ['icao' => 'B753', 'description' => 'Boeing 757-300 pax', 'wtc' => 'H'],
|
||||
'757' => ['icao' => 'n/a', 'description' => 'Boeing 757 all pax models', 'wtc' => 'H'],
|
||||
'75F' => ['icao' => 'B752', 'description' => 'Boeing 757 Freighter', 'wtc' => 'H'],
|
||||
'75M' => ['icao' => 'B752', 'description' => 'Boeing 757 Mixed Configuration', 'wtc' => 'H'],
|
||||
'75T' => ['icao' => 'B753', 'description' => 'Boeing 757-300 (winglets) pax', 'wtc' => 'H'],
|
||||
'75W' => ['icao' => 'B752', 'description' => 'Boeing 757-200 (winglets) pax', 'wtc' => 'H'],
|
||||
'762' => ['icao' => 'B762', 'description' => 'Boeing 767-200 pax', 'wtc' => 'H'],
|
||||
'763' => ['icao' => 'B763', 'description' => 'Boeing 767-300 pax', 'wtc' => 'H'],
|
||||
'764' => ['icao' => 'B764', 'description' => 'Boeing 767-400 pax', 'wtc' => 'H'],
|
||||
'767' => ['icao' => 'n/a', 'description' => 'Boeing 767 all pax models', 'wtc' => 'H'],
|
||||
'76F' => ['icao' => 'n/a', 'description' => 'Boeing 767 all Freighter models', 'wtc' => 'H'],
|
||||
'76W' => ['icao' => 'B763', 'description' => 'Boeing 767-300 (winglets) pax', 'wtc' => 'H'],
|
||||
'76V' => ['icao' => 'B763', 'description' => 'Boeing 767-300 (winglets) Freighter', 'wtc' => 'H'],
|
||||
'76X' => ['icao' => 'B762', 'description' => 'Boeing 767-200 Freighter', 'wtc' => 'H'],
|
||||
'76Y' => ['icao' => 'B763', 'description' => 'Boeing 767-300 Freighter', 'wtc' => 'H'],
|
||||
'772' => ['icao' => 'B772', 'description' => 'Boeing 777-200 pax', 'wtc' => 'H'],
|
||||
'773' => ['icao' => 'B773', 'description' => 'Boeing 777-300 pax', 'wtc' => 'H'],
|
||||
'777' => ['icao' => 'n/a', 'description' => 'Boeing 777 all pax models', 'wtc' => 'H'],
|
||||
'77F' => ['icao' => 'n/a', 'description' => 'Boeing 777 Freighter', 'wtc' => 'H'],
|
||||
'77L' => ['icao' => 'B772', 'description' => 'Boeing 777-200LR pax', 'wtc' => 'H'],
|
||||
'77W' => ['icao' => 'B77W', 'description' => 'Boeing 777-300ER pax', 'wtc' => 'H'],
|
||||
'77X' => ['icao' => 'B77L', 'description' => 'Boeing 777-200 Freighter', 'wtc' => 'H'],
|
||||
'781' => ['icao' => 'B78X', 'description' => 'Boeing 787-10 pax', 'wtc' => 'H'],
|
||||
'788' => ['icao' => 'B788', 'description' => 'Boeing 787-8 pax', 'wtc' => 'H'],
|
||||
'789' => ['icao' => 'B789', 'description' => 'Boeing 787-9 pax', 'wtc' => 'H'],
|
||||
'7M7' => ['icao' => 'B37M', 'description' => 'Boeing 737 MAX 7 pax', 'wtc' => 'M'],
|
||||
'7M8' => ['icao' => 'B38M', 'description' => 'Boeing 737 MAX 8 pax', 'wtc' => 'M'],
|
||||
'7M9' => ['icao' => 'B39M', 'description' => 'Boeing 737 MAX 9 pax', 'wtc' => 'M'],
|
||||
'7MJ' => ['icao' => 'B3XM', 'description' => 'Boeing 737 MAX 10 pax', 'wtc' => 'M'],
|
||||
'A22' => ['icao' => 'AN22', 'description' => 'Antonov AN-22', 'wtc' => 'L'],
|
||||
'A26' => ['icao' => 'AN26', 'description' => 'Antonov AN-26', 'wtc' => 'L'],
|
||||
'A28' => ['icao' => 'AN28', 'description' => 'Antonov AN-28 / PZL Miele M-28 Skytruck', 'wtc' => 'L'],
|
||||
'A30' => ['icao' => 'AN30', 'description' => 'Antonov AN-30', 'wtc' => 'L'],
|
||||
'A32' => ['icao' => 'AN32', 'description' => 'Antonov AN-32', 'wtc' => 'L'],
|
||||
'A38' => ['icao' => 'AN38', 'description' => 'Antonov AN-38', 'wtc' => 'L'],
|
||||
'A40' => ['icao' => 'A140', 'description' => 'Antonov AN-140', 'wtc' => 'M'],
|
||||
'A4F' => ['icao' => 'A124', 'description' => 'Antonov AN-124 Ruslan', 'wtc' => 'H'],
|
||||
'A5F' => ['icao' => 'A225', 'description' => 'Antonov AN-225', 'wtc' => 'H'],
|
||||
'A81' => ['icao' => 'A148', 'description' => 'Antonov AN-148-100', 'wtc' => 'M'],
|
||||
'AB3' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300 pax', 'wtc' => 'H'],
|
||||
'AB4' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300B2/B4/C4 pax', 'wtc' => 'H'],
|
||||
'AB6' => ['icao' => 'A306', 'description' => 'Airbus Industrie A300-600 pax', 'wtc' => 'H'],
|
||||
'ABB' => ['icao' => 'A3ST', 'description' => 'Airbus Industrie A300-600ST Beluga Freighter', 'wtc' => 'H'],
|
||||
'ABF' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300 Freighter', 'wtc' => 'H'],
|
||||
'ABX' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300C4/F4 Freighter', 'wtc' => 'H'],
|
||||
'ABY' => ['icao' => 'A306', 'description' => 'Airbus Industrie A300-600 Freighter', 'wtc' => 'H'],
|
||||
'ACD' => ['icao' => 'n/a', 'description' => 'Gulfstream/Rockwell (Aero) Commander/Turbo Commander', 'wtc' => 'L'],
|
||||
'ACP' => ['icao' => 'AC68', 'description' => 'Gulfstream/Rockwell (Aero) Commander', 'wtc' => 'L'],
|
||||
'ACT' => ['icao' => 'AC90', 'description' => 'Gulfstream/Rockwell (Aero) Turbo Commander', 'wtc' => 'L'],
|
||||
'AGH' => ['icao' => 'A109', 'description' => 'Agusta A109', 'wtc' => 'n/a'],
|
||||
'ALM' => ['icao' => 'LOAD', 'description' => 'Ayres LM-200 Loadmaster', 'wtc' => 'M'],
|
||||
'AN4' => ['icao' => 'AN24', 'description' => 'Antonov AN-24', 'wtc' => 'M'],
|
||||
'AN6' => ['icao' => 'n/a', 'description' => 'Antonov AN-26 / AN-30 / AN-32', 'wtc' => 'M'],
|
||||
'AN7' => ['icao' => 'AN72', 'description' => 'Antonov AN-72 / AN-74', 'wtc' => 'M'],
|
||||
'ANF' => ['icao' => 'AN12', 'description' => 'Antonov AN-12', 'wtc' => 'M'],
|
||||
'APH' => ['icao' => 'n/a', 'description' => 'Eurocopter (Aerospatiale) SA330 Puma / AS332 Super Puma', 'wtc' => 'n/a'],
|
||||
'AR1' => ['icao' => 'RJ1H', 'description' => 'Avro RJ100 Avroliner', 'wtc' => 'M'],
|
||||
'AR7' => ['icao' => 'RJ70', 'description' => 'Avro RJ70 Avroliner', 'wtc' => 'M'],
|
||||
'AR8' => ['icao' => 'RJ85', 'description' => 'Avro RJ85 Avroliner', 'wtc' => 'M'],
|
||||
'ARJ' => ['icao' => 'n/a', 'description' => 'Avro RJ70 / RJ85 / RJ100 Avroliner', 'wtc' => 'M'],
|
||||
'ARX' => ['icao' => 'n/a', 'description' => 'Avro RJX85 / RJX100', 'wtc' => 'M'],
|
||||
'AT4' => ['icao' => 'AT43', 'description' => 'Aerospatiale/Alenia ATR 42-300 / 320', 'wtc' => 'M'],
|
||||
'AT5' => ['icao' => 'AT45', 'description' => 'Aerospatiale/Alenia ATR 42-500', 'wtc' => 'M'],
|
||||
'AT7' => ['icao' => 'AT72', 'description' => 'Aerospatiale/Alenia ATR 72', 'wtc' => 'M'],
|
||||
'ATD' => ['icao' => 'AT44', 'description' => 'Aerospatiale/Alenia ATR 42-400', 'wtc' => 'M'],
|
||||
'ATF' => ['icao' => 'AT72', 'description' => 'Aerospatiale/Alenia ATR 72 Freighter', 'wtc' => 'M'],
|
||||
'ATP' => ['icao' => 'ATP', 'description' => 'British Aerospace ATP', 'wtc' => 'M'],
|
||||
'ATR' => ['icao' => 'n/a', 'description' => 'Aerospatiale/Alenia ATR 42 / ATR 72', 'wtc' => 'M'],
|
||||
'AX1' => ['icao' => 'RX1H', 'description' => 'Avro RJX100', 'wtc' => 'M'],
|
||||
'AX8' => ['icao' => 'RX85', 'description' => 'Avro RJX85', 'wtc' => 'M'],
|
||||
'B11' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven / RomBAC One Eleven', 'wtc' => 'M'],
|
||||
'B12' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 200', 'wtc' => 'M'],
|
||||
'B13' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 300', 'wtc' => 'M'],
|
||||
'B14' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 400/475', 'wtc' => 'M'],
|
||||
'B15' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 500 / RomBAC One Eleven', 'wtc' => 'M'],
|
||||
'B72' => ['icao' => 'B720', 'description' => 'Boeing 720B pax', 'wtc' => 'M'],
|
||||
'BE1' => ['icao' => 'B190', 'description' => 'Beechcraft 1900/1900C/1900D', 'wtc' => 'M'],
|
||||
'BE2' => ['icao' => 'n/a', 'description' => 'Beechcraft twin piston engines', 'wtc' => 'L'],
|
||||
'BEC' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft', 'wtc' => 'L'],
|
||||
'BEH' => ['icao' => 'B190', 'description' => 'Beechcraft 1900D', 'wtc' => 'M'],
|
||||
'BEP' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft - single engine', 'wtc' => 'L'],
|
||||
'BES' => ['icao' => 'B190', 'description' => 'Beechcraft 1900/1900C', 'wtc' => 'M'],
|
||||
'BET' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft - twin turboprop engine', 'wtc' => 'L'],
|
||||
'BH2' => ['icao' => 'n/a', 'description' => 'Bell Helicopters', 'wtc' => 'n/a'],
|
||||
'BNI' => ['icao' => 'BN2P', 'description' => 'Pilatus Britten-Norman BN-2A/B Islander', 'wtc' => 'L'],
|
||||
'BNT' => ['icao' => 'TRIS', 'description' => 'Pilatus Britten-Norman BN-2A Mk III Trislander', 'wtc' => 'L'],
|
||||
'BUS' => ['icao' => 'n/a', 'description' => 'Bus', 'wtc' => 'n/a'],
|
||||
'C27' => ['icao' => 'AJ27', 'description' => 'COMAC ARJ21', 'wtc' => 'M'],
|
||||
'CCJ' => ['icao' => 'CL60', 'description' => 'Canadair Challenger', 'wtc' => 'M'],
|
||||
'CCX' => ['icao' => 'GLEX', 'description' => 'Canadair Global Express', 'wtc' => 'M'],
|
||||
'CD2' => ['icao' => 'NOMA', 'description' => 'Government Aircraft Factories N22B / N24A Nomad', 'wtc' => 'L'],
|
||||
'CL4' => ['icao' => 'CL44', 'description' => 'Canadair CL-44', 'wtc' => 'M'],
|
||||
'919' => ['icao' => 'C919', 'description' => 'Comac C919 Passenger', 'wtc' => 'M'],
|
||||
'CN1' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - single piston engine', 'wtc' => 'L'],
|
||||
'CN2' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - twin piston engines', 'wtc' => 'L'],
|
||||
'CN7' => ['icao' => 'C750', 'description' => 'Cessna 750 Citation X', 'wtc' => 'M'],
|
||||
'CNA' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft', 'wtc' => 'L'],
|
||||
'CNC' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - single turboprop engine', 'wtc' => 'L'],
|
||||
'CNJ' => ['icao' => 'n/a', 'description' => 'Cessna Citation', 'wtc' => 'L'],
|
||||
'CNT' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - twin turboprop engines', 'wtc' => 'L'],
|
||||
'CR1' => ['icao' => 'CRJ1', 'description' => 'Canadair Regional Jet 100', 'wtc' => 'M'],
|
||||
'CR2' => ['icao' => 'CRJ2', 'description' => 'Canadair Regional Jet 200', 'wtc' => 'M'],
|
||||
'CR7' => ['icao' => 'CRJ7', 'description' => 'Canadair Regional Jet 700', 'wtc' => 'M'],
|
||||
'CR9' => ['icao' => 'CRJ9', 'description' => 'Canadair Regional Jet 900', 'wtc' => 'M'],
|
||||
'CRA' => ['icao' => 'CRJ9', 'description' => 'Canadair Regional Jet 705', 'wtc' => 'M'],
|
||||
'CRF' => ['icao' => 'n/a', 'description' => 'Canadair Regional Jet Freighter', 'wtc' => 'M'],
|
||||
'CRJ' => ['icao' => 'n/a', 'description' => 'Canadair Regional Jet', 'wtc' => 'M'],
|
||||
'CRK' => ['icao' => 'CRJX', 'description' => 'Canadair Regional Jet 1000', 'wtc' => 'M'],
|
||||
'CRV' => ['icao' => 'S210', 'description' => 'Aerospatiale (Sud Aviation) Se.210 Caravelle', 'wtc' => 'M'],
|
||||
'CS1' => ['icao' => 'BCS1', 'description' => 'Bombardier C Series CS100', 'wtc' => 'M'],
|
||||
'CS2' => ['icao' => 'C212', 'description' => 'CASA / IPTN 212 Aviocar', 'wtc' => 'M'],
|
||||
'CS3' => ['icao' => 'BCS3', 'description' => 'Bombardier C Series CS300', 'wtc' => 'M'],
|
||||
'CS5' => ['icao' => 'CN35', 'description' => 'CASA / IPTN CN-235', 'wtc' => 'M'],
|
||||
'CV2' => ['icao' => 'CVLP', 'description' => 'Convair CV-240 pax', 'wtc' => 'M'],
|
||||
'CV4' => ['icao' => 'CVLP', 'description' => 'Convair CV-440 Metropolitan pax', 'wtc' => 'M'],
|
||||
'CV5' => ['icao' => 'CVLT', 'description' => 'Convair CV-580 pax', 'wtc' => 'M'],
|
||||
'CVF' => ['icao' => 'n/a', 'description' => 'Convair CV-240 / 440 / 580 / 600 / 640 Freighter', 'wtc' => 'M'],
|
||||
'CVR' => ['icao' => 'n/a', 'description' => 'Convair CV-240 / 440 / 580 / 600 / 640 pax', 'wtc' => 'M'],
|
||||
'CVV' => ['icao' => 'CVLP', 'description' => 'Convair CV-240 Freighter', 'wtc' => 'M'],
|
||||
'CVX' => ['icao' => 'CVLP', 'description' => 'Convair CV-440 Freighter', 'wtc' => 'M'],
|
||||
'CVY' => ['icao' => 'CVLT', 'description' => 'Convair CV-580 / 600 / 640 Freighter', 'wtc' => 'M'],
|
||||
'CWC' => ['icao' => 'C46', 'description' => 'Curtiss C-46 Commando', 'wtc' => 'M'],
|
||||
'D10' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 pax', 'wtc' => 'H'],
|
||||
'D11' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-10/15 pax', 'wtc' => 'H'],
|
||||
'D1C' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-30/40 pax', 'wtc' => 'H'],
|
||||
'D1F' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 all Freighters', 'wtc' => 'H'],
|
||||
'D1M' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 all Combi models', 'wtc' => 'H'],
|
||||
'D1X' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-10 Freighter', 'wtc' => 'H'],
|
||||
'D1Y' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-30 / 40 Freighters', 'wtc' => 'H'],
|
||||
'D28' => ['icao' => 'D228', 'description' => 'Fairchild Dornier Do.228', 'wtc' => 'L'],
|
||||
'D38' => ['icao' => 'D328', 'description' => 'Fairchild Dornier Do.328', 'wtc' => 'M'],
|
||||
'D3F' => ['icao' => 'DC3', 'description' => 'Douglas DC-3 Freighter', 'wtc' => 'M'],
|
||||
'D6F' => ['icao' => 'DC6', 'description' => 'Douglas DC-6A/B/C Freighter', 'wtc' => 'M'],
|
||||
'D8F' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all Freighters', 'wtc' => 'H'],
|
||||
'D8L' => ['icao' => 'DC86', 'description' => 'Douglas DC-8-62 pax', 'wtc' => 'H'],
|
||||
'D8M' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all Combi models', 'wtc' => 'H'],
|
||||
'D8Q' => ['icao' => 'DC87', 'description' => 'Douglas DC-8-72 pax', 'wtc' => 'H'],
|
||||
'D8T' => ['icao' => 'DC85', 'description' => 'Douglas DC-8-50 Freighter', 'wtc' => 'H'],
|
||||
'D8X' => ['icao' => 'n/a', 'description' => 'Douglas DC-8-61 / 62 / 63 Freighters', 'wtc' => 'H'],
|
||||
'D8Y' => ['icao' => 'DC87', 'description' => 'Douglas DC-8-71 / 72 / 73 Freighters', 'wtc' => 'H'],
|
||||
'D91' => ['icao' => 'DC91', 'description' => 'Douglas DC-9-10 pax', 'wtc' => 'M'],
|
||||
'D92' => ['icao' => 'DC92', 'description' => 'Douglas DC-9-20 pax', 'wtc' => 'M'],
|
||||
'D93' => ['icao' => 'DC93', 'description' => 'Douglas DC-9-30 pax', 'wtc' => 'M'],
|
||||
'D94' => ['icao' => 'DC94', 'description' => 'Douglas DC-9-40 pax', 'wtc' => 'M'],
|
||||
'D95' => ['icao' => 'DC95', 'description' => 'Douglas DC-9-50 pax', 'wtc' => 'M'],
|
||||
'D9C' => ['icao' => 'DC93', 'description' => 'Douglas DC-9-30 Freighter', 'wtc' => 'M'],
|
||||
'D9D' => ['icao' => 'DC94', 'description' => 'Douglas DC-9-40 Freighter', 'wtc' => 'M'],
|
||||
'D9F' => ['icao' => 'n/a', 'description' => 'Douglas DC-9 all Freighters', 'wtc' => 'M'],
|
||||
'D9X' => ['icao' => 'DC91', 'description' => 'Douglas DC-9-10 Freighter', 'wtc' => 'M'],
|
||||
'DC3' => ['icao' => 'DC3', 'description' => 'Douglas DC-3 pax', 'wtc' => 'M'],
|
||||
'DC4' => ['icao' => 'DC4', 'description' => 'Douglas DC-4 pax', 'wtc' => 'M'],
|
||||
'DC6' => ['icao' => 'DC6', 'description' => 'Douglas DC6A/B pax', 'wtc' => 'M'],
|
||||
'DC8' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all pax models', 'wtc' => 'H'],
|
||||
'DC9' => ['icao' => 'DC9', 'description' => 'Douglas DC-9 all pax models', 'wtc' => 'M'],
|
||||
'DF2' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon 10 / 100 / 20 / 200 / 2000', 'wtc' => 'M'],
|
||||
'DF3' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon 50 / 900', 'wtc' => 'M'],
|
||||
'DFL' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon', 'wtc' => 'M'],
|
||||
'DH1' => ['icao' => 'DH8A', 'description' => 'De Havilland Canada DHC-8-100 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH2' => ['icao' => 'DH8B', 'description' => 'De Havilland Canada DHC-8-200 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH3' => ['icao' => 'DH8C', 'description' => 'De Havilland Canada DHC-8-300 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH4' => ['icao' => 'DH8D', 'description' => 'De Havilland Canada DHC-8-400 Dash 8Q', 'wtc' => 'M'],
|
||||
'DH7' => ['icao' => 'DHC7', 'description' => 'De Havilland Canada DHC-7 Dash 7', 'wtc' => 'M'],
|
||||
'DH8' => ['icao' => 'n/a', 'description' => 'De Havilland Canada DHC-8 Dash 8 all models', 'wtc' => 'M'],
|
||||
'DHB' => ['icao' => 'n/a', 'description' => 'De Havilland Canada DHC-2 Beaver / Turbo Beaver', 'wtc' => 'L'],
|
||||
'DHC' => ['icao' => 'DHC4', 'description' => 'De Havilland Canada DHC-4 Caribou', 'wtc' => 'M'],
|
||||
'DHD' => ['icao' => 'DOVE', 'description' => 'De Havilland DH.104 Dove', 'wtc' => 'L'],
|
||||
'DHH' => ['icao' => 'HERN', 'description' => 'De Havilland DH.114 Heron', 'wtc' => 'L'],
|
||||
'DHL' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Turbo Otter', 'wtc' => 'L'],
|
||||
'DHO' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Otter / Turbo Otter', 'wtc' => 'L'],
|
||||
'DHP' => ['icao' => 'DHC2', 'description' => 'De Havilland Canada DHC-2 Beaver', 'wtc' => 'L'],
|
||||
'DHR' => ['icao' => 'DH2T', 'description' => 'De Havilland Canada DHC-2 Turbo-Beaver', 'wtc' => 'L'],
|
||||
'DHS' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Otter', 'wtc' => 'L'],
|
||||
'DHT' => ['icao' => 'DHC6', 'description' => 'De Havilland Canada DHC-6 Twin Otter', 'wtc' => 'L'],
|
||||
'E70' => ['icao' => 'E170', 'description' => 'Embraer 170', 'wtc' => 'M'],
|
||||
'E75' => ['icao' => 'E75L', 'description' => 'Embraer 175 (Short wing / Long Wing)', 'wtc' => 'M'],
|
||||
'E90' => ['icao' => 'E190', 'description' => 'Embraer 190', 'wtc' => 'M'],
|
||||
'E95' => ['icao' => 'E195', 'description' => 'Embraer 195', 'wtc' => 'M'],
|
||||
'EC3' => ['icao' => 'EC30', 'description' => 'Eurocopter EC.130', 'wtc' => 'n/a'],
|
||||
'EM2' => ['icao' => 'E120', 'description' => 'Embraer EMB.120 Brasilia', 'wtc' => 'L'],
|
||||
'EMB' => ['icao' => 'E110', 'description' => 'Embraer EMB.110 Bandeirnate', 'wtc' => 'M'],
|
||||
'EMJ' => ['icao' => 'n/a', 'description' => 'Embraer 170/190', 'wtc' => 'M'],
|
||||
'ER3' => ['icao' => 'E135', 'description' => 'Embraer RJ135 / Legacy 600 / Legacy 650', 'wtc' => 'M'],
|
||||
'ER4' => ['icao' => 'E145', 'description' => 'Embraer RJ145 Amazon', 'wtc' => 'M'],
|
||||
'ERD' => ['icao' => 'n/a', 'description' => 'Embraer RJ140', 'wtc' => 'M'],
|
||||
'ERJ' => ['icao' => 'n/a', 'description' => 'Embraer RJ135 / RJ140 / RJ145', 'wtc' => 'M'],
|
||||
'F21' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 1000', 'wtc' => 'M'],
|
||||
'F22' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 2000', 'wtc' => 'M'],
|
||||
'F23' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 3000', 'wtc' => 'M'],
|
||||
'F24' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 4000', 'wtc' => 'M'],
|
||||
'F27' => ['icao' => 'F27', 'description' => 'Fokker F.27 Friendship / Fairchild F.27', 'wtc' => 'M'],
|
||||
'F28' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship', 'wtc' => 'M'],
|
||||
'F50' => ['icao' => 'F50', 'description' => 'Fokker 50', 'wtc' => 'M'],
|
||||
'F5F' => ['icao' => 'F50', 'description' => 'Fokker 50 Freighter', 'wtc' => 'M'],
|
||||
'F70' => ['icao' => 'F70', 'description' => 'Fokker 70', 'wtc' => 'M'],
|
||||
'FA7' => ['icao' => 'n/a', 'description' => 'Fairchild Dornier 728JET', 'wtc' => 'M'],
|
||||
'FK7' => ['icao' => 'F27', 'description' => 'Fairchild FH.227', 'wtc' => 'M'],
|
||||
'FRJ' => ['icao' => 'J328', 'description' => 'Fairchild Dornier 328JET', 'wtc' => 'M'],
|
||||
'GRG' => ['icao' => 'G21', 'description' => 'Grumman G.21 Goose', 'wtc' => 'L'],
|
||||
'GRJ' => ['icao' => 'n/a', 'description' => 'Gulfstream Aerospace G-1159 Gulfstream II / III / IV / V', 'wtc' => 'M'],
|
||||
'GRM' => ['icao' => 'G73T', 'description' => 'Grumman G.73 Turbo Mallard', 'wtc' => 'L'],
|
||||
'GRS' => ['icao' => 'G159', 'description' => 'Gulfstream Aerospace G-159 Gulfstream I', 'wtc' => 'M'],
|
||||
'H25' => ['icao' => 'n/a', 'description' => 'British Aerospace (Hawker Siddeley) HS.125', 'wtc' => 'M'],
|
||||
'HEC' => ['icao' => 'COUC', 'description' => 'Helio H-250 Courier / H-295 / 385 Super Courier', 'wtc' => 'L'],
|
||||
'HOV' => ['icao' => 'n/a', 'description' => 'Hovercraft', 'wtc' => 'n/a'],
|
||||
'HS7' => ['icao' => 'A748', 'description' => 'Hawker Siddeley HS.748', 'wtc' => 'M'],
|
||||
'I14' => ['icao' => 'I114', 'description' => 'Ilyushin IL114', 'wtc' => 'M'],
|
||||
'I93' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96-300 pax', 'wtc' => 'H'],
|
||||
'I9F' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96 Freighters', 'wtc' => 'H'],
|
||||
'I9M' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96M pax', 'wtc' => 'H'],
|
||||
'I9X' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96-300 Freighter', 'wtc' => 'H'],
|
||||
'I9Y' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96T Freighter', 'wtc' => 'H'],
|
||||
'IL6' => ['icao' => 'IL62', 'description' => 'Ilyushin IL62', 'wtc' => 'H'],
|
||||
'IL7' => ['icao' => 'IL76', 'description' => 'Ilyushin IL76', 'wtc' => 'H'],
|
||||
'IL8' => ['icao' => 'IL18', 'description' => 'Ilyushin IL18', 'wtc' => 'M'],
|
||||
'IL9' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96 pax', 'wtc' => 'H'],
|
||||
'ILW' => ['icao' => 'IL86', 'description' => 'Ilyushin IL86', 'wtc' => 'H'],
|
||||
'J31' => ['icao' => 'JS31', 'description' => 'British Aerospace Jetstream 31', 'wtc' => 'L'],
|
||||
'J32' => ['icao' => 'JS32', 'description' => 'British Aerospace Jetstream 32', 'wtc' => 'L'],
|
||||
'J41' => ['icao' => 'JS41', 'description' => 'British Aerospace Jetstream 41', 'wtc' => 'M'],
|
||||
'JST' => ['icao' => 'n/a', 'description' => 'British Aerospace Jetstream 31 / 32 / 41', 'wtc' => 'L/M'],
|
||||
'JU5' => ['icao' => 'JU52', 'description' => 'Junkers Ju52/3M', 'wtc' => 'M'],
|
||||
'L10' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 Tristar pax', 'wtc' => 'H'],
|
||||
'L11' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 1 / 50 / 100 / 150 / 200 / 250 Tristar pax', 'wtc' => 'H'],
|
||||
'L15' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 500 Tristar pax', 'wtc' => 'H'],
|
||||
'L1F' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 Tristar Freighter', 'wtc' => 'H'],
|
||||
'L49' => ['icao' => 'CONI', 'description' => 'Lockheed L-1049 Super Constellation', 'wtc' => 'M'],
|
||||
'L4T' => ['icao' => 'L410', 'description' => 'LET 410', 'wtc' => 'L'],
|
||||
'LCH' => ['icao' => 'n/a', 'description' => 'Launch - Boat', 'wtc' => 'n/a'],
|
||||
'LMO' => ['icao' => 'n/a', 'description' => 'Limousine', 'wtc' => 'n/a'],
|
||||
'LOE' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra pax', 'wtc' => 'M'],
|
||||
'LOF' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra Freighter', 'wtc' => 'M'],
|
||||
'LOH' => ['icao' => 'C130', 'description' => 'Lockheed L-182 / 282 / 382 (L-100) Hercules', 'wtc' => 'M'],
|
||||
'LOM' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra Mixed Configuration', 'wtc' => 'M'],
|
||||
'LRJ' => ['icao' => 'n/a', 'description' => 'Gates Learjet', 'wtc' => 'M'],
|
||||
'M11' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 pax', 'wtc' => 'H'],
|
||||
'M1F' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 Freighter', 'wtc' => 'H'],
|
||||
'M1M' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 Mixed Configuration', 'wtc' => 'H'],
|
||||
'M80' => ['icao' => 'MD80', 'description' => 'McDonnell Douglas MD80', 'wtc' => 'M'],
|
||||
'M81' => ['icao' => 'MD81', 'description' => 'McDonnell Douglas MD81', 'wtc' => 'M'],
|
||||
'M82' => ['icao' => 'MD82', 'description' => 'McDonnell Douglas MD82', 'wtc' => 'M'],
|
||||
'M83' => ['icao' => 'MD83', 'description' => 'McDonnell Douglas MD83', 'wtc' => 'M'],
|
||||
'M87' => ['icao' => 'MD87', 'description' => 'McDonnell Douglas MD87', 'wtc' => 'M'],
|
||||
'M88' => ['icao' => 'MD88', 'description' => 'McDonnell Douglas MD88', 'wtc' => 'M'],
|
||||
'M90' => ['icao' => 'MD90', 'description' => 'McDonnell Douglas MD90', 'wtc' => 'M'],
|
||||
'MBH' => ['icao' => 'B105', 'description' => 'Eurocopter (MBB) Bo.105', 'wtc' => 'n/a'],
|
||||
'MD9' => ['icao' => 'EXPL', 'description' => 'MD Helicopters MD900 Explorer', 'wtc' => 'n/a'],
|
||||
'MIH' => ['icao' => 'MI8', 'description' => 'MIL Mi-8 / Mi-17 / Mi-171 / Mil-172', 'wtc' => 'n/a'],
|
||||
'MU2' => ['icao' => 'MU2', 'description' => 'Mitsubishi Mu-2', 'wtc' => 'L'],
|
||||
'ND2' => ['icao' => 'N262', 'description' => 'Aerospatiale (Nord) 262', 'wtc' => 'M'],
|
||||
'NDC' => ['icao' => 'S601', 'description' => 'Aerospatiale SN.601 Corvette', 'wtc' => 'L'],
|
||||
'NDE' => ['icao' => 'n/a', 'description' => 'Eurocopter (Aerospatiale) AS350 Ecureuil / AS355 Ecureuil 2', 'wtc' => 'n/a'],
|
||||
'NDH' => ['icao' => 'S65C', 'description' => 'Eurocopter (Aerospatiale) SA365C / SA365N Dauphin 2', 'wtc' => 'n/a'],
|
||||
'PA1' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - single piston engine', 'wtc' => 'L'],
|
||||
'PA2' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - twin piston engines', 'wtc' => 'L'],
|
||||
'PAG' => ['icao' => 'n/a', 'description' => 'Piper light aircraft', 'wtc' => 'L'],
|
||||
'PAT' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - twin turboprop engines', 'wtc' => 'L'],
|
||||
'PL2' => ['icao' => 'PC12', 'description' => 'Pilatus PC-12', 'wtc' => 'L'],
|
||||
'PL6' => ['icao' => 'PC6T', 'description' => 'Pilatus PC-6 Turbo Porter', 'wtc' => 'L'],
|
||||
'PN6' => ['icao' => 'P68', 'description' => 'Partenavia P.68', 'wtc' => 'L'],
|
||||
'RFS' => ['icao' => 'n/a', 'description' => 'Road Feeder Service - Cargo Truck', 'wtc' => 'n/a'],
|
||||
'S20' => ['icao' => 'SB20', 'description' => 'Saab 2000', 'wtc' => 'M'],
|
||||
'S58' => ['icao' => 'S58T', 'description' => 'Sikorsky S-58T', 'wtc' => 'n/a'],
|
||||
'S61' => ['icao' => 'S61', 'description' => 'Sikorsky S-61', 'wtc' => 'n/a'],
|
||||
'S76' => ['icao' => 'S76', 'description' => 'Sikorsky S-76', 'wtc' => 'n/a'],
|
||||
'SF3' => ['icao' => 'SF34', 'description' => 'Saab SF340', 'wtc' => 'M'],
|
||||
'SFB' => ['icao' => 'SF34', 'description' => 'Saab SF340B', 'wtc' => 'M'],
|
||||
'SFF' => ['icao' => 'SF34', 'description' => 'Saab SF340 Freighter', 'wtc' => 'M'],
|
||||
'SH3' => ['icao' => 'SH33', 'description' => 'Shorts SD.330', 'wtc' => 'M'],
|
||||
'SH6' => ['icao' => 'SH36', 'description' => 'Shorts SD.360', 'wtc' => 'M'],
|
||||
'SHB' => ['icao' => 'BELF', 'description' => 'Shorts SC-5 Belfast', 'wtc' => 'M'],
|
||||
'SHS' => ['icao' => 'SC7', 'description' => 'Shorts SC-7 Skyvan', 'wtc' => 'L'],
|
||||
'SSC' => ['icao' => 'CONC', 'description' => 'Aerospatiale/BAC Concorde', 'wtc' => 'H'],
|
||||
'SU1' => ['icao' => '', 'description' => 'Sukhoi Superjet 100', 'wtc' => 'M'],
|
||||
'SU7' => ['icao' => '', 'description' => 'Sukhoi Superjet 100-75', 'wtc' => 'M'],
|
||||
'SU9' => ['icao' => 'SU95', 'description' => 'Sukhoi Superjet 100-95', 'wtc' => 'M'],
|
||||
'SWM' => ['icao' => 'n/a', 'description' => 'Fairchild (Swearingen) SA26 / SA226 / SA227 Metro / Merlin / Expediter', 'wtc' => 'L'],
|
||||
'T20' => ['icao' => 'T204', 'description' => 'Tupolev Tu-204 / Tu-214', 'wtc' => 'M'],
|
||||
'T2F' => ['icao' => 'T204', 'description' => 'Tupolev Tu-204 Freighter', 'wtc' => 'M'],
|
||||
'TRN' => ['icao' => 'n/a', 'description' => 'Train', 'wtc' => 'n/a'],
|
||||
'TU3' => ['icao' => 'T134', 'description' => 'Tupolev Tu134', 'wtc' => 'M'],
|
||||
'TU5' => ['icao' => 'T154', 'description' => 'Tupolev Tu154', 'wtc' => 'M'],
|
||||
'VCV' => ['icao' => 'VISC', 'description' => 'Vickers Viscount', 'wtc' => 'M'],
|
||||
'WWP' => ['icao' => 'WW24', 'description' => 'Israel Aircraft Industries 1124 Westwind', 'wtc' => 'M'],
|
||||
'YK2' => ['icao' => 'YK42', 'description' => 'Yakovlev Yak 42', 'wtc' => 'M'],
|
||||
'YK4' => ['icao' => 'YK40', 'description' => 'Yakovlev Yak 40', 'wtc' => 'M'],
|
||||
'YN2' => ['icao' => 'Y12', 'description' => 'Harbin Yunshuji Y12', 'wtc' => 'M'],
|
||||
'YN7' => ['icao' => 'AN24', 'description' => 'Xian Yunshuji Y7', 'wtc' => 'M'],
|
||||
'YS1' => ['icao' => 'YS11', 'description' => 'NAMC YS-11', 'wtc' => 'M'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Achievement::whereInternalName('fun_challenges.canadian_provinces')->update(['difficulty_description' => null]);
|
||||
|
||||
Aircraft::create([
|
||||
'designator' => 'A330',
|
||||
'manufacturer_code' => 'AIRBUS',
|
||||
'model_full_name' => 'A-330 Family',
|
||||
'aircraft_description' => 'LandPlane',
|
||||
'engine_type' => 'Jet',
|
||||
'engine_count' => 2,
|
||||
'wtc' => 'H',
|
||||
]);
|
||||
|
||||
Aircraft::create([
|
||||
'designator' => 'A340',
|
||||
'manufacturer_code' => 'AIRBUS',
|
||||
'model_full_name' => 'A-340 Family',
|
||||
'aircraft_description' => 'LandPlane',
|
||||
'engine_type' => 'Jet',
|
||||
'engine_count' => 4,
|
||||
'wtc' => 'H',
|
||||
]);
|
||||
|
||||
Schema::table('aircraft', function (Blueprint $table) {
|
||||
$table->string('iata_code', 10)->nullable()->after('designator');
|
||||
});
|
||||
|
||||
$updated = 0;
|
||||
$unmatched = [];
|
||||
|
||||
foreach ($this->aircraft_codes as $iata => $data) {
|
||||
$icao = $data['icao'];
|
||||
|
||||
if (empty($icao) || $icao === 'n/a') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows = DB::table('aircraft')->where('designator', $icao)->update([
|
||||
'iata_code' => $iata,
|
||||
]);
|
||||
|
||||
if ($rows === 0) {
|
||||
$unmatched[] = "{$iata} → {$icao} ({$data['description']})";
|
||||
}
|
||||
|
||||
$updated += $rows;
|
||||
}
|
||||
|
||||
echo "Updated {$updated} aircraft record(s) with IATA codes.\n";
|
||||
|
||||
if (!empty($unmatched)) {
|
||||
echo "\n=== IATA codes with no matching ICAO in aircraft table ===\n";
|
||||
foreach ($unmatched as $line) {
|
||||
echo " {$line}\n";
|
||||
}
|
||||
echo "==========================================================\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,496 @@
|
||||
<?php
|
||||
|
||||
use App\Models\IataEquipmentCode;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
protected array $aircraft_codes = [
|
||||
'100' => ['icao' => 'F100', 'description' => 'Fokker 100', 'wtc' => 'M'],
|
||||
'141' => ['icao' => 'B461', 'description' => 'BAe 146-100 Pax', 'wtc' => 'M'],
|
||||
'142' => ['icao' => 'B462', 'description' => 'BAe 146-200 Pax', 'wtc' => 'M'],
|
||||
'143' => ['icao' => 'B463', 'description' => 'BAe 146-300 Pax', 'wtc' => 'M'],
|
||||
'146' => ['icao' => '', 'description' => 'BAe 146 all pax models', 'wtc' => 'M'],
|
||||
'14F' => ['icao' => '', 'description' => 'BAe 146 Freighter (-100/200/300QT & QC)', 'wtc' => 'M'],
|
||||
'14X' => ['icao' => 'B461', 'description' => 'BAe 146 Freighter (-100QT & QC)', 'wtc' => 'M'],
|
||||
'14Y' => ['icao' => 'B462', 'description' => 'BAe 146 Freighter (-200QT & QC)', 'wtc' => 'M'],
|
||||
'14Z' => ['icao' => 'B463', 'description' => 'BAe 146 Freighter (-200QT & QC)', 'wtc' => 'M'],
|
||||
'290' => ['icao' => 'E290', 'description' => 'Embraer E190-E2', 'wtc' => 'M'],
|
||||
'295' => ['icao' => 'E295', 'description' => 'Embraer E195-E2', 'wtc' => 'M'],
|
||||
'221' => ['icao' => 'BCS1', 'description' => 'Airbus A220-100', 'wtc' => 'M'],
|
||||
'223' => ['icao' => 'BCS3', 'description' => 'Airbus A220-200', 'wtc' => 'M'],
|
||||
'310' => ['icao' => 'A310', 'description' => 'Airbus A310 all pax models', 'wtc' => 'H'],
|
||||
'312' => ['icao' => 'A310', 'description' => 'Airbus A310-200 pax', 'wtc' => 'H'],
|
||||
'313' => ['icao' => 'A310', 'description' => 'Airbus A310-300 pax', 'wtc' => 'H'],
|
||||
'318' => ['icao' => 'A318', 'description' => 'Airbus A318', 'wtc' => 'M'],
|
||||
'319' => ['icao' => 'A319', 'description' => 'Airbus A319 Ceo', 'wtc' => 'M'],
|
||||
'31F' => ['icao' => 'A310', 'description' => 'Airbus A310 Freighter', 'wtc' => 'M'],
|
||||
'31N' => ['icao' => 'A19N', 'description' => 'Airbus A319 Neo', 'wtc' => 'M'],
|
||||
'31X' => ['icao' => 'A310', 'description' => 'Airbus A310-200 Freighter', 'wtc' => 'M'],
|
||||
'31Y' => ['icao' => 'A310', 'description' => 'Airbus A310-300 Freighter', 'wtc' => 'M'],
|
||||
'320' => ['icao' => 'A320', 'description' => 'Airbus A320-100/200 Ceo', 'wtc' => 'M'],
|
||||
'321' => ['icao' => 'A321', 'description' => 'Airbus A321-100/200 Ceo', 'wtc' => 'M'],
|
||||
'32A' => ['icao' => 'A320', 'description' => 'Airbus A320-200 Ceo (Sharklets)', 'wtc' => 'M'],
|
||||
'32C' => ['icao' => 'A318', 'description' => 'Airbus A318 (Sharklets)', 'wtc' => 'M'],
|
||||
'32D' => ['icao' => 'A319', 'description' => 'Airbus A319 Ceo (Sharklets)', 'wtc' => 'M'],
|
||||
'32N' => ['icao' => 'A20N', 'description' => 'Airbus A320-200 Neo', 'wtc' => 'M'],
|
||||
'32Q' => ['icao' => 'A21N', 'description' => 'Airbus A321-200 Neo', 'wtc' => 'M'],
|
||||
'32S' => ['icao' => 'n/a', 'description' => 'Airbus A318/319/320/321', 'wtc' => 'M'],
|
||||
'330' => ['icao' => 'A330', 'description' => 'Airbus A330 all models', 'wtc' => 'H'],
|
||||
'332' => ['icao' => 'A332', 'description' => 'Airbus A330-200', 'wtc' => 'H'],
|
||||
'333' => ['icao' => 'A333', 'description' => 'Airbus A330-300', 'wtc' => 'H'],
|
||||
'338' => ['icao' => 'A338', 'description' => 'Airbus A330-800 Neo', 'wtc' => 'H'],
|
||||
'339' => ['icao' => 'A339', 'description' => 'Airbus A330-900 Neo', 'wtc' => 'H'],
|
||||
'33X' => ['icao' => 'A332', 'description' => 'Airbus A330-200 Freighter', 'wtc' => 'H'],
|
||||
'340' => ['icao' => 'A340', 'description' => 'Airbus A340 all models', 'wtc' => 'H'],
|
||||
'342' => ['icao' => 'A342', 'description' => 'Airbus A340-200', 'wtc' => 'H'],
|
||||
'343' => ['icao' => 'A343', 'description' => 'Airbus A340-300', 'wtc' => 'H'],
|
||||
'345' => ['icao' => 'A345', 'description' => 'Airbus A340-500', 'wtc' => 'H'],
|
||||
'346' => ['icao' => 'A346', 'description' => 'Airbus A340-600', 'wtc' => 'H'],
|
||||
'351' => ['icao' => 'A35K', 'description' => 'Airbus A350-1000', 'wtc' => 'H'],
|
||||
'359' => ['icao' => 'A359', 'description' => 'Airbus A350-900', 'wtc' => 'H'],
|
||||
'380' => ['icao' => 'A388', 'description' => 'Airbus A380 pax', 'wtc' => 'J'],
|
||||
'38F' => ['icao' => '', 'description' => 'Airbus A380 Freighter', 'wtc' => 'J'],
|
||||
'703' => ['icao' => 'B703', 'description' => 'Boeing 707-300 pax', 'wtc' => 'H'],
|
||||
'707' => ['icao' => 'n/a', 'description' => 'Boeing 707/720 all pax models', 'wtc' => 'H'],
|
||||
'70F' => ['icao' => 'B703', 'description' => 'Boeing 707 Freighter', 'wtc' => 'H'],
|
||||
'70M' => ['icao' => 'B703', 'description' => 'Boeing 707 Combi', 'wtc' => 'H'],
|
||||
'717' => ['icao' => 'B712', 'description' => 'Boeing 717', 'wtc' => 'M'],
|
||||
'721' => ['icao' => 'B721', 'description' => 'Boeing 727-100 pax', 'wtc' => 'M'],
|
||||
'722' => ['icao' => 'B722', 'description' => 'Boeing 727-200 pax', 'wtc' => 'M'],
|
||||
'727' => ['icao' => 'n/a', 'description' => 'Boeing 727 all pax models', 'wtc' => 'M'],
|
||||
'72B' => ['icao' => 'B721', 'description' => 'Boeing 727-100 Mixed Configuration', 'wtc' => 'M'],
|
||||
'72C' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Mixed Configuration', 'wtc' => 'M'],
|
||||
'72F' => ['icao' => 'n/a', 'description' => 'Boeing 727 Freighter (-100/200)', 'wtc' => 'M'],
|
||||
'72M' => ['icao' => 'n/a', 'description' => 'Boeing 727 Combi', 'wtc' => 'M'],
|
||||
'72S' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Advanced pax', 'wtc' => 'M'],
|
||||
'72W' => ['icao' => 'B721', 'description' => 'Boeing 727-200 (winglets) pax', 'wtc' => 'M'],
|
||||
'72X' => ['icao' => 'B721', 'description' => 'Boeing 727-100 Freighter', 'wtc' => 'M'],
|
||||
'72Y' => ['icao' => 'B722', 'description' => 'Boeing 727-200 Freighter', 'wtc' => 'M'],
|
||||
'731' => ['icao' => 'B731', 'description' => 'Boeing 737-100 pax', 'wtc' => 'M'],
|
||||
'732' => ['icao' => 'B732', 'description' => 'Boeing 737-200 pax', 'wtc' => 'M'],
|
||||
'733' => ['icao' => 'B733', 'description' => 'Boeing 737-300 pax', 'wtc' => 'M'],
|
||||
'734' => ['icao' => 'B734', 'description' => 'Boeing 737-400 pax', 'wtc' => 'M'],
|
||||
'735' => ['icao' => 'B735', 'description' => 'Boeing 737-500 pax', 'wtc' => 'M'],
|
||||
'736' => ['icao' => 'B736', 'description' => 'Boeing 737-600 pax', 'wtc' => 'M'],
|
||||
'737' => ['icao' => 'n/a', 'description' => 'Boeing 737 all pax models', 'wtc' => 'M'],
|
||||
'738' => ['icao' => 'B738', 'description' => 'Boeing 737-800 pax', 'wtc' => 'M'],
|
||||
'739' => ['icao' => 'B739', 'description' => 'Boeing 737-900 pax', 'wtc' => 'M'],
|
||||
'73C' => ['icao' => 'B733', 'description' => 'Boeing 737-300 (winglets) pax', 'wtc' => 'M'],
|
||||
'73E' => ['icao' => 'B735', 'description' => 'Boeing 737-500 (winglets) pax', 'wtc' => 'M'],
|
||||
'73F' => ['icao' => 'n/a', 'description' => 'Boeing 737 all Freighter models', 'wtc' => 'M'],
|
||||
'73G' => ['icao' => 'B737', 'description' => 'Boeing 737-700 pax', 'wtc' => 'M'],
|
||||
'73H' => ['icao' => 'B738', 'description' => 'Boeing 737-800 (winglets) pax', 'wtc' => 'M'],
|
||||
'73J' => ['icao' => 'B739', 'description' => 'Boeing 737-900 (winglets) pax', 'wtc' => 'M'],
|
||||
'73L' => ['icao' => 'B732', 'description' => 'Boeing 737-200 Combi', 'wtc' => 'M'],
|
||||
'73M' => ['icao' => 'n/a', 'description' => 'Boeing 737 Combi', 'wtc' => 'M'],
|
||||
'73P' => ['icao' => 'B734', 'description' => 'Boeing 737-400 Freighter', 'wtc' => 'M'],
|
||||
'73Q' => ['icao' => 'B734', 'description' => 'Boeing 737-400 Combi', 'wtc' => 'M'],
|
||||
'73R' => ['icao' => 'B737', 'description' => 'Boeing 737-700 Combi', 'wtc' => 'M'],
|
||||
'73W' => ['icao' => 'B737', 'description' => 'Boeing 737-700 (winglets) pax', 'wtc' => 'M'],
|
||||
'73X' => ['icao' => 'B732', 'description' => 'Boeing 737-200 Freighter', 'wtc' => 'M'],
|
||||
'73Y' => ['icao' => 'B733', 'description' => 'Boeing 737-300 Freighter', 'wtc' => 'M'],
|
||||
'741' => ['icao' => 'B741', 'description' => 'Boeing 747-100 pax', 'wtc' => 'H'],
|
||||
'742' => ['icao' => 'B742', 'description' => 'Boeing 747-200 pax', 'wtc' => 'H'],
|
||||
'743' => ['icao' => 'B743', 'description' => 'Boeing 747-300 pax', 'wtc' => 'H'],
|
||||
'744' => ['icao' => 'B744', 'description' => 'Boeing 747-400 pax', 'wtc' => 'H'],
|
||||
'747' => ['icao' => 'n/a', 'description' => 'Boeing 747 all pax models', 'wtc' => 'H'],
|
||||
'748' => ['icao' => 'B748', 'description' => 'Boeing 747-8 pax', 'wtc' => 'H'],
|
||||
'74B' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Swingtail Freighter', 'wtc' => 'H'],
|
||||
'74C' => ['icao' => 'B742', 'description' => 'Boeing 747-200 Combi', 'wtc' => 'H'],
|
||||
'74D' => ['icao' => 'B743', 'description' => 'Boeing 747-300 Combi', 'wtc' => 'H'],
|
||||
'74E' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Combi', 'wtc' => 'H'],
|
||||
'74F' => ['icao' => 'n/a', 'description' => 'Boeing 747 all Freighter models', 'wtc' => 'H'],
|
||||
'74H' => ['icao' => 'n/a', 'description' => 'Boeing 747-8I Passenger', 'wtc' => 'H'],
|
||||
'74J' => ['icao' => 'B744', 'description' => 'Boeing 747-400 (Domestic) pax', 'wtc' => 'H'],
|
||||
'74L' => ['icao' => 'B74S', 'description' => 'Boeing 747SP', 'wtc' => 'H'],
|
||||
'74M' => ['icao' => 'n/a', 'description' => 'Boeing 747 all Combi models', 'wtc' => 'H'],
|
||||
'74N' => ['icao' => 'n/a', 'description' => 'Boeing 747-8F', 'wtc' => 'H'],
|
||||
'74R' => ['icao' => 'B74R', 'description' => 'Boeing 747SR pax', 'wtc' => 'H'],
|
||||
'74T' => ['icao' => 'B741', 'description' => 'Boeing 747-100 Freighter', 'wtc' => 'H'],
|
||||
'74U' => ['icao' => 'B743', 'description' => 'Boeing 747-300 / 747-200 SUD Freighter', 'wtc' => 'H'],
|
||||
'74V' => ['icao' => 'B74R', 'description' => 'Boeing 747SR Freighter', 'wtc' => 'H'],
|
||||
'74X' => ['icao' => 'B742', 'description' => 'Boeing 747-200 Freighter', 'wtc' => 'H'],
|
||||
'74Y' => ['icao' => 'B744', 'description' => 'Boeing 747-400 Freighter', 'wtc' => 'H'],
|
||||
'752' => ['icao' => 'B752', 'description' => 'Boeing 757-200 pax', 'wtc' => 'H'],
|
||||
'753' => ['icao' => 'B753', 'description' => 'Boeing 757-300 pax', 'wtc' => 'H'],
|
||||
'757' => ['icao' => 'n/a', 'description' => 'Boeing 757 all pax models', 'wtc' => 'H'],
|
||||
'75F' => ['icao' => 'B752', 'description' => 'Boeing 757 Freighter', 'wtc' => 'H'],
|
||||
'75M' => ['icao' => 'B752', 'description' => 'Boeing 757 Mixed Configuration', 'wtc' => 'H'],
|
||||
'75T' => ['icao' => 'B753', 'description' => 'Boeing 757-300 (winglets) pax', 'wtc' => 'H'],
|
||||
'75W' => ['icao' => 'B752', 'description' => 'Boeing 757-200 (winglets) pax', 'wtc' => 'H'],
|
||||
'762' => ['icao' => 'B762', 'description' => 'Boeing 767-200 pax', 'wtc' => 'H'],
|
||||
'763' => ['icao' => 'B763', 'description' => 'Boeing 767-300 pax', 'wtc' => 'H'],
|
||||
'764' => ['icao' => 'B764', 'description' => 'Boeing 767-400 pax', 'wtc' => 'H'],
|
||||
'767' => ['icao' => 'n/a', 'description' => 'Boeing 767 all pax models', 'wtc' => 'H'],
|
||||
'76F' => ['icao' => 'n/a', 'description' => 'Boeing 767 all Freighter models', 'wtc' => 'H'],
|
||||
'76W' => ['icao' => 'B763', 'description' => 'Boeing 767-300 (winglets) pax', 'wtc' => 'H'],
|
||||
'76V' => ['icao' => 'B763', 'description' => 'Boeing 767-300 (winglets) Freighter', 'wtc' => 'H'],
|
||||
'76X' => ['icao' => 'B762', 'description' => 'Boeing 767-200 Freighter', 'wtc' => 'H'],
|
||||
'76Y' => ['icao' => 'B763', 'description' => 'Boeing 767-300 Freighter', 'wtc' => 'H'],
|
||||
'772' => ['icao' => 'B772', 'description' => 'Boeing 777-200 pax', 'wtc' => 'H'],
|
||||
'773' => ['icao' => 'B773', 'description' => 'Boeing 777-300 pax', 'wtc' => 'H'],
|
||||
'777' => ['icao' => 'n/a', 'description' => 'Boeing 777 all pax models', 'wtc' => 'H'],
|
||||
'77F' => ['icao' => 'n/a', 'description' => 'Boeing 777 Freighter', 'wtc' => 'H'],
|
||||
'77L' => ['icao' => 'B772', 'description' => 'Boeing 777-200LR pax', 'wtc' => 'H'],
|
||||
'77W' => ['icao' => 'B77W', 'description' => 'Boeing 777-300ER pax', 'wtc' => 'H'],
|
||||
'77X' => ['icao' => 'B77L', 'description' => 'Boeing 777-200 Freighter', 'wtc' => 'H'],
|
||||
'781' => ['icao' => 'B78X', 'description' => 'Boeing 787-10 pax', 'wtc' => 'H'],
|
||||
'788' => ['icao' => 'B788', 'description' => 'Boeing 787-8 pax', 'wtc' => 'H'],
|
||||
'789' => ['icao' => 'B789', 'description' => 'Boeing 787-9 pax', 'wtc' => 'H'],
|
||||
'7M7' => ['icao' => 'B37M', 'description' => 'Boeing 737 MAX 7 pax', 'wtc' => 'M'],
|
||||
'7M8' => ['icao' => 'B38M', 'description' => 'Boeing 737 MAX 8 pax', 'wtc' => 'M'],
|
||||
'7M9' => ['icao' => 'B39M', 'description' => 'Boeing 737 MAX 9 pax', 'wtc' => 'M'],
|
||||
'7MJ' => ['icao' => 'B3XM', 'description' => 'Boeing 737 MAX 10 pax', 'wtc' => 'M'],
|
||||
'A22' => ['icao' => 'AN22', 'description' => 'Antonov AN-22', 'wtc' => 'L'],
|
||||
'A26' => ['icao' => 'AN26', 'description' => 'Antonov AN-26', 'wtc' => 'L'],
|
||||
'A28' => ['icao' => 'AN28', 'description' => 'Antonov AN-28 / PZL Miele M-28 Skytruck', 'wtc' => 'L'],
|
||||
'A30' => ['icao' => 'AN30', 'description' => 'Antonov AN-30', 'wtc' => 'L'],
|
||||
'A32' => ['icao' => 'AN32', 'description' => 'Antonov AN-32', 'wtc' => 'L'],
|
||||
'A38' => ['icao' => 'AN38', 'description' => 'Antonov AN-38', 'wtc' => 'L'],
|
||||
'A40' => ['icao' => 'A140', 'description' => 'Antonov AN-140', 'wtc' => 'M'],
|
||||
'A4F' => ['icao' => 'A124', 'description' => 'Antonov AN-124 Ruslan', 'wtc' => 'H'],
|
||||
'A5F' => ['icao' => 'A225', 'description' => 'Antonov AN-225', 'wtc' => 'H'],
|
||||
'A81' => ['icao' => 'A148', 'description' => 'Antonov AN-148-100', 'wtc' => 'M'],
|
||||
'AB3' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300 pax', 'wtc' => 'H'],
|
||||
'AB4' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300B2/B4/C4 pax', 'wtc' => 'H'],
|
||||
'AB6' => ['icao' => 'A306', 'description' => 'Airbus Industrie A300-600 pax', 'wtc' => 'H'],
|
||||
'ABB' => ['icao' => 'A3ST', 'description' => 'Airbus Industrie A300-600ST Beluga Freighter', 'wtc' => 'H'],
|
||||
'ABF' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300 Freighter', 'wtc' => 'H'],
|
||||
'ABX' => ['icao' => 'A30B', 'description' => 'Airbus Industrie A300C4/F4 Freighter', 'wtc' => 'H'],
|
||||
'ABY' => ['icao' => 'A306', 'description' => 'Airbus Industrie A300-600 Freighter', 'wtc' => 'H'],
|
||||
'ACD' => ['icao' => 'n/a', 'description' => 'Gulfstream/Rockwell (Aero) Commander/Turbo Commander', 'wtc' => 'L'],
|
||||
'ACP' => ['icao' => 'AC68', 'description' => 'Gulfstream/Rockwell (Aero) Commander', 'wtc' => 'L'],
|
||||
'ACT' => ['icao' => 'AC90', 'description' => 'Gulfstream/Rockwell (Aero) Turbo Commander', 'wtc' => 'L'],
|
||||
'AGH' => ['icao' => 'A109', 'description' => 'Agusta A109', 'wtc' => 'n/a'],
|
||||
'ALM' => ['icao' => 'LOAD', 'description' => 'Ayres LM-200 Loadmaster', 'wtc' => 'M'],
|
||||
'AN4' => ['icao' => 'AN24', 'description' => 'Antonov AN-24', 'wtc' => 'M'],
|
||||
'AN6' => ['icao' => 'n/a', 'description' => 'Antonov AN-26 / AN-30 / AN-32', 'wtc' => 'M'],
|
||||
'AN7' => ['icao' => 'AN72', 'description' => 'Antonov AN-72 / AN-74', 'wtc' => 'M'],
|
||||
'ANF' => ['icao' => 'AN12', 'description' => 'Antonov AN-12', 'wtc' => 'M'],
|
||||
'APH' => ['icao' => 'n/a', 'description' => 'Eurocopter (Aerospatiale) SA330 Puma / AS332 Super Puma', 'wtc' => 'n/a'],
|
||||
'AR1' => ['icao' => 'RJ1H', 'description' => 'Avro RJ100 Avroliner', 'wtc' => 'M'],
|
||||
'AR7' => ['icao' => 'RJ70', 'description' => 'Avro RJ70 Avroliner', 'wtc' => 'M'],
|
||||
'AR8' => ['icao' => 'RJ85', 'description' => 'Avro RJ85 Avroliner', 'wtc' => 'M'],
|
||||
'ARJ' => ['icao' => 'n/a', 'description' => 'Avro RJ70 / RJ85 / RJ100 Avroliner', 'wtc' => 'M'],
|
||||
'ARX' => ['icao' => 'n/a', 'description' => 'Avro RJX85 / RJX100', 'wtc' => 'M'],
|
||||
'AT4' => ['icao' => 'AT43', 'description' => 'Aerospatiale/Alenia ATR 42-300 / 320', 'wtc' => 'M'],
|
||||
'AT5' => ['icao' => 'AT45', 'description' => 'Aerospatiale/Alenia ATR 42-500', 'wtc' => 'M'],
|
||||
'AT7' => ['icao' => 'AT72', 'description' => 'Aerospatiale/Alenia ATR 72', 'wtc' => 'M'],
|
||||
'ATD' => ['icao' => 'AT44', 'description' => 'Aerospatiale/Alenia ATR 42-400', 'wtc' => 'M'],
|
||||
'ATF' => ['icao' => 'AT72', 'description' => 'Aerospatiale/Alenia ATR 72 Freighter', 'wtc' => 'M'],
|
||||
'ATP' => ['icao' => 'ATP', 'description' => 'British Aerospace ATP', 'wtc' => 'M'],
|
||||
'ATR' => ['icao' => 'n/a', 'description' => 'Aerospatiale/Alenia ATR 42 / ATR 72', 'wtc' => 'M'],
|
||||
'AX1' => ['icao' => 'RX1H', 'description' => 'Avro RJX100', 'wtc' => 'M'],
|
||||
'AX8' => ['icao' => 'RX85', 'description' => 'Avro RJX85', 'wtc' => 'M'],
|
||||
'B11' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven / RomBAC One Eleven', 'wtc' => 'M'],
|
||||
'B12' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 200', 'wtc' => 'M'],
|
||||
'B13' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 300', 'wtc' => 'M'],
|
||||
'B14' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 400/475', 'wtc' => 'M'],
|
||||
'B15' => ['icao' => 'BA11', 'description' => 'British Aerospace (BAC) One Eleven 500 / RomBAC One Eleven', 'wtc' => 'M'],
|
||||
'B72' => ['icao' => 'B720', 'description' => 'Boeing 720B pax', 'wtc' => 'M'],
|
||||
'BE1' => ['icao' => 'B190', 'description' => 'Beechcraft 1900/1900C/1900D', 'wtc' => 'M'],
|
||||
'BE2' => ['icao' => 'n/a', 'description' => 'Beechcraft twin piston engines', 'wtc' => 'L'],
|
||||
'BEC' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft', 'wtc' => 'L'],
|
||||
'BEH' => ['icao' => 'B190', 'description' => 'Beechcraft 1900D', 'wtc' => 'M'],
|
||||
'BEP' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft - single engine', 'wtc' => 'L'],
|
||||
'BES' => ['icao' => 'B190', 'description' => 'Beechcraft 1900/1900C', 'wtc' => 'M'],
|
||||
'BET' => ['icao' => 'n/a', 'description' => 'Beechcraft light aircraft - twin turboprop engine', 'wtc' => 'L'],
|
||||
'BH2' => ['icao' => 'n/a', 'description' => 'Bell Helicopters', 'wtc' => 'n/a'],
|
||||
'BNI' => ['icao' => 'BN2P', 'description' => 'Pilatus Britten-Norman BN-2A/B Islander', 'wtc' => 'L'],
|
||||
'BNT' => ['icao' => 'TRIS', 'description' => 'Pilatus Britten-Norman BN-2A Mk III Trislander', 'wtc' => 'L'],
|
||||
'BUS' => ['icao' => 'n/a', 'description' => 'Bus', 'wtc' => 'n/a'],
|
||||
'C27' => ['icao' => 'AJ27', 'description' => 'COMAC ARJ21', 'wtc' => 'M'],
|
||||
'CCJ' => ['icao' => 'CL60', 'description' => 'Canadair Challenger', 'wtc' => 'M'],
|
||||
'CCX' => ['icao' => 'GLEX', 'description' => 'Canadair Global Express', 'wtc' => 'M'],
|
||||
'CD2' => ['icao' => 'NOMA', 'description' => 'Government Aircraft Factories N22B / N24A Nomad', 'wtc' => 'L'],
|
||||
'CL4' => ['icao' => 'CL44', 'description' => 'Canadair CL-44', 'wtc' => 'M'],
|
||||
'919' => ['icao' => 'C919', 'description' => 'Comac C919 Passenger', 'wtc' => 'M'],
|
||||
'CN1' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - single piston engine', 'wtc' => 'L'],
|
||||
'CN2' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - twin piston engines', 'wtc' => 'L'],
|
||||
'CN7' => ['icao' => 'C750', 'description' => 'Cessna 750 Citation X', 'wtc' => 'M'],
|
||||
'CNA' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft', 'wtc' => 'L'],
|
||||
'CNC' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - single turboprop engine', 'wtc' => 'L'],
|
||||
'CNJ' => ['icao' => 'n/a', 'description' => 'Cessna Citation', 'wtc' => 'L'],
|
||||
'CNT' => ['icao' => 'n/a', 'description' => 'Cessna light aircraft - twin turboprop engines', 'wtc' => 'L'],
|
||||
'CR1' => ['icao' => 'CRJ1', 'description' => 'Canadair Regional Jet 100', 'wtc' => 'M'],
|
||||
'CR2' => ['icao' => 'CRJ2', 'description' => 'Canadair Regional Jet 200', 'wtc' => 'M'],
|
||||
'CR7' => ['icao' => 'CRJ7', 'description' => 'Canadair Regional Jet 700', 'wtc' => 'M'],
|
||||
'CR9' => ['icao' => 'CRJ9', 'description' => 'Canadair Regional Jet 900', 'wtc' => 'M'],
|
||||
'CRA' => ['icao' => 'CRJ9', 'description' => 'Canadair Regional Jet 705', 'wtc' => 'M'],
|
||||
'CRF' => ['icao' => 'n/a', 'description' => 'Canadair Regional Jet Freighter', 'wtc' => 'M'],
|
||||
'CRJ' => ['icao' => 'n/a', 'description' => 'Canadair Regional Jet', 'wtc' => 'M'],
|
||||
'CRK' => ['icao' => 'CRJX', 'description' => 'Canadair Regional Jet 1000', 'wtc' => 'M'],
|
||||
'CRV' => ['icao' => 'S210', 'description' => 'Aerospatiale (Sud Aviation) Se.210 Caravelle', 'wtc' => 'M'],
|
||||
'CS1' => ['icao' => 'BCS1', 'description' => 'Bombardier C Series CS100', 'wtc' => 'M'],
|
||||
'CS2' => ['icao' => 'C212', 'description' => 'CASA / IPTN 212 Aviocar', 'wtc' => 'M'],
|
||||
'CS3' => ['icao' => 'BCS3', 'description' => 'Bombardier C Series CS300', 'wtc' => 'M'],
|
||||
'CS5' => ['icao' => 'CN35', 'description' => 'CASA / IPTN CN-235', 'wtc' => 'M'],
|
||||
'CV2' => ['icao' => 'CVLP', 'description' => 'Convair CV-240 pax', 'wtc' => 'M'],
|
||||
'CV4' => ['icao' => 'CVLP', 'description' => 'Convair CV-440 Metropolitan pax', 'wtc' => 'M'],
|
||||
'CV5' => ['icao' => 'CVLT', 'description' => 'Convair CV-580 pax', 'wtc' => 'M'],
|
||||
'CVF' => ['icao' => 'n/a', 'description' => 'Convair CV-240 / 440 / 580 / 600 / 640 Freighter', 'wtc' => 'M'],
|
||||
'CVR' => ['icao' => 'n/a', 'description' => 'Convair CV-240 / 440 / 580 / 600 / 640 pax', 'wtc' => 'M'],
|
||||
'CVV' => ['icao' => 'CVLP', 'description' => 'Convair CV-240 Freighter', 'wtc' => 'M'],
|
||||
'CVX' => ['icao' => 'CVLP', 'description' => 'Convair CV-440 Freighter', 'wtc' => 'M'],
|
||||
'CVY' => ['icao' => 'CVLT', 'description' => 'Convair CV-580 / 600 / 640 Freighter', 'wtc' => 'M'],
|
||||
'CWC' => ['icao' => 'C46', 'description' => 'Curtiss C-46 Commando', 'wtc' => 'M'],
|
||||
'D10' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 pax', 'wtc' => 'H'],
|
||||
'D11' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-10/15 pax', 'wtc' => 'H'],
|
||||
'D1C' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-30/40 pax', 'wtc' => 'H'],
|
||||
'D1F' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 all Freighters', 'wtc' => 'H'],
|
||||
'D1M' => ['icao' => 'DC10', 'description' => 'Douglas DC-10 all Combi models', 'wtc' => 'H'],
|
||||
'D1X' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-10 Freighter', 'wtc' => 'H'],
|
||||
'D1Y' => ['icao' => 'DC10', 'description' => 'Douglas DC-10-30 / 40 Freighters', 'wtc' => 'H'],
|
||||
'D28' => ['icao' => 'D228', 'description' => 'Fairchild Dornier Do.228', 'wtc' => 'L'],
|
||||
'D38' => ['icao' => 'D328', 'description' => 'Fairchild Dornier Do.328', 'wtc' => 'M'],
|
||||
'D3F' => ['icao' => 'DC3', 'description' => 'Douglas DC-3 Freighter', 'wtc' => 'M'],
|
||||
'D6F' => ['icao' => 'DC6', 'description' => 'Douglas DC-6A/B/C Freighter', 'wtc' => 'M'],
|
||||
'D8F' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all Freighters', 'wtc' => 'H'],
|
||||
'D8L' => ['icao' => 'DC86', 'description' => 'Douglas DC-8-62 pax', 'wtc' => 'H'],
|
||||
'D8M' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all Combi models', 'wtc' => 'H'],
|
||||
'D8Q' => ['icao' => 'DC87', 'description' => 'Douglas DC-8-72 pax', 'wtc' => 'H'],
|
||||
'D8T' => ['icao' => 'DC85', 'description' => 'Douglas DC-8-50 Freighter', 'wtc' => 'H'],
|
||||
'D8X' => ['icao' => 'n/a', 'description' => 'Douglas DC-8-61 / 62 / 63 Freighters', 'wtc' => 'H'],
|
||||
'D8Y' => ['icao' => 'DC87', 'description' => 'Douglas DC-8-71 / 72 / 73 Freighters', 'wtc' => 'H'],
|
||||
'D91' => ['icao' => 'DC91', 'description' => 'Douglas DC-9-10 pax', 'wtc' => 'M'],
|
||||
'D92' => ['icao' => 'DC92', 'description' => 'Douglas DC-9-20 pax', 'wtc' => 'M'],
|
||||
'D93' => ['icao' => 'DC93', 'description' => 'Douglas DC-9-30 pax', 'wtc' => 'M'],
|
||||
'D94' => ['icao' => 'DC94', 'description' => 'Douglas DC-9-40 pax', 'wtc' => 'M'],
|
||||
'D95' => ['icao' => 'DC95', 'description' => 'Douglas DC-9-50 pax', 'wtc' => 'M'],
|
||||
'D9C' => ['icao' => 'DC93', 'description' => 'Douglas DC-9-30 Freighter', 'wtc' => 'M'],
|
||||
'D9D' => ['icao' => 'DC94', 'description' => 'Douglas DC-9-40 Freighter', 'wtc' => 'M'],
|
||||
'D9F' => ['icao' => 'n/a', 'description' => 'Douglas DC-9 all Freighters', 'wtc' => 'M'],
|
||||
'D9X' => ['icao' => 'DC91', 'description' => 'Douglas DC-9-10 Freighter', 'wtc' => 'M'],
|
||||
'DC3' => ['icao' => 'DC3', 'description' => 'Douglas DC-3 pax', 'wtc' => 'M'],
|
||||
'DC4' => ['icao' => 'DC4', 'description' => 'Douglas DC-4 pax', 'wtc' => 'M'],
|
||||
'DC6' => ['icao' => 'DC6', 'description' => 'Douglas DC6A/B pax', 'wtc' => 'M'],
|
||||
'DC8' => ['icao' => 'n/a', 'description' => 'Douglas DC-8 all pax models', 'wtc' => 'H'],
|
||||
'DC9' => ['icao' => 'DC9', 'description' => 'Douglas DC-9 all pax models', 'wtc' => 'M'],
|
||||
'DF2' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon 10 / 100 / 20 / 200 / 2000', 'wtc' => 'M'],
|
||||
'DF3' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon 50 / 900', 'wtc' => 'M'],
|
||||
'DFL' => ['icao' => 'n/a', 'description' => 'Dassault (Breguet Mystere) Falcon', 'wtc' => 'M'],
|
||||
'DH1' => ['icao' => 'DH8A', 'description' => 'De Havilland Canada DHC-8-100 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH2' => ['icao' => 'DH8B', 'description' => 'De Havilland Canada DHC-8-200 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH3' => ['icao' => 'DH8C', 'description' => 'De Havilland Canada DHC-8-300 Dash 8 / 8Q', 'wtc' => 'M'],
|
||||
'DH4' => ['icao' => 'DH8D', 'description' => 'De Havilland Canada DHC-8-400 Dash 8Q', 'wtc' => 'M'],
|
||||
'DH7' => ['icao' => 'DHC7', 'description' => 'De Havilland Canada DHC-7 Dash 7', 'wtc' => 'M'],
|
||||
'DH8' => ['icao' => 'n/a', 'description' => 'De Havilland Canada DHC-8 Dash 8 all models', 'wtc' => 'M'],
|
||||
'DHB' => ['icao' => 'n/a', 'description' => 'De Havilland Canada DHC-2 Beaver / Turbo Beaver', 'wtc' => 'L'],
|
||||
'DHC' => ['icao' => 'DHC4', 'description' => 'De Havilland Canada DHC-4 Caribou', 'wtc' => 'M'],
|
||||
'DHD' => ['icao' => 'DOVE', 'description' => 'De Havilland DH.104 Dove', 'wtc' => 'L'],
|
||||
'DHH' => ['icao' => 'HERN', 'description' => 'De Havilland DH.114 Heron', 'wtc' => 'L'],
|
||||
'DHL' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Turbo Otter', 'wtc' => 'L'],
|
||||
'DHO' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Otter / Turbo Otter', 'wtc' => 'L'],
|
||||
'DHP' => ['icao' => 'DHC2', 'description' => 'De Havilland Canada DHC-2 Beaver', 'wtc' => 'L'],
|
||||
'DHR' => ['icao' => 'DH2T', 'description' => 'De Havilland Canada DHC-2 Turbo-Beaver', 'wtc' => 'L'],
|
||||
'DHS' => ['icao' => 'DHC3', 'description' => 'De Havilland Canada DHC-3 Otter', 'wtc' => 'L'],
|
||||
'DHT' => ['icao' => 'DHC6', 'description' => 'De Havilland Canada DHC-6 Twin Otter', 'wtc' => 'L'],
|
||||
'E70' => ['icao' => 'E170', 'description' => 'Embraer 170', 'wtc' => 'M'],
|
||||
'E75' => ['icao' => 'E75L', 'description' => 'Embraer 175 (Short wing / Long Wing)', 'wtc' => 'M'],
|
||||
'E90' => ['icao' => 'E190', 'description' => 'Embraer 190', 'wtc' => 'M'],
|
||||
'E95' => ['icao' => 'E195', 'description' => 'Embraer 195', 'wtc' => 'M'],
|
||||
'EC3' => ['icao' => 'EC30', 'description' => 'Eurocopter EC.130', 'wtc' => 'n/a'],
|
||||
'EM2' => ['icao' => 'E120', 'description' => 'Embraer EMB.120 Brasilia', 'wtc' => 'L'],
|
||||
'EMB' => ['icao' => 'E110', 'description' => 'Embraer EMB.110 Bandeirnate', 'wtc' => 'M'],
|
||||
'EMJ' => ['icao' => 'n/a', 'description' => 'Embraer 170/190', 'wtc' => 'M'],
|
||||
'ER3' => ['icao' => 'E135', 'description' => 'Embraer RJ135 / Legacy 600 / Legacy 650', 'wtc' => 'M'],
|
||||
'ER4' => ['icao' => 'E145', 'description' => 'Embraer RJ145 Amazon', 'wtc' => 'M'],
|
||||
'ERD' => ['icao' => 'n/a', 'description' => 'Embraer RJ140', 'wtc' => 'M'],
|
||||
'ERJ' => ['icao' => 'n/a', 'description' => 'Embraer RJ135 / RJ140 / RJ145', 'wtc' => 'M'],
|
||||
'F21' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 1000', 'wtc' => 'M'],
|
||||
'F22' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 2000', 'wtc' => 'M'],
|
||||
'F23' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 3000', 'wtc' => 'M'],
|
||||
'F24' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship 4000', 'wtc' => 'M'],
|
||||
'F27' => ['icao' => 'F27', 'description' => 'Fokker F.27 Friendship / Fairchild F.27', 'wtc' => 'M'],
|
||||
'F28' => ['icao' => 'F28', 'description' => 'Fokker F.28 Fellowship', 'wtc' => 'M'],
|
||||
'F50' => ['icao' => 'F50', 'description' => 'Fokker 50', 'wtc' => 'M'],
|
||||
'F5F' => ['icao' => 'F50', 'description' => 'Fokker 50 Freighter', 'wtc' => 'M'],
|
||||
'F70' => ['icao' => 'F70', 'description' => 'Fokker 70', 'wtc' => 'M'],
|
||||
'FA7' => ['icao' => 'n/a', 'description' => 'Fairchild Dornier 728JET', 'wtc' => 'M'],
|
||||
'FK7' => ['icao' => 'F27', 'description' => 'Fairchild FH.227', 'wtc' => 'M'],
|
||||
'FRJ' => ['icao' => 'J328', 'description' => 'Fairchild Dornier 328JET', 'wtc' => 'M'],
|
||||
'GRG' => ['icao' => 'G21', 'description' => 'Grumman G.21 Goose', 'wtc' => 'L'],
|
||||
'GRJ' => ['icao' => 'n/a', 'description' => 'Gulfstream Aerospace G-1159 Gulfstream II / III / IV / V', 'wtc' => 'M'],
|
||||
'GRM' => ['icao' => 'G73T', 'description' => 'Grumman G.73 Turbo Mallard', 'wtc' => 'L'],
|
||||
'GRS' => ['icao' => 'G159', 'description' => 'Gulfstream Aerospace G-159 Gulfstream I', 'wtc' => 'M'],
|
||||
'H25' => ['icao' => 'n/a', 'description' => 'British Aerospace (Hawker Siddeley) HS.125', 'wtc' => 'M'],
|
||||
'HEC' => ['icao' => 'COUC', 'description' => 'Helio H-250 Courier / H-295 / 385 Super Courier', 'wtc' => 'L'],
|
||||
'HOV' => ['icao' => 'n/a', 'description' => 'Hovercraft', 'wtc' => 'n/a'],
|
||||
'HS7' => ['icao' => 'A748', 'description' => 'Hawker Siddeley HS.748', 'wtc' => 'M'],
|
||||
'I14' => ['icao' => 'I114', 'description' => 'Ilyushin IL114', 'wtc' => 'M'],
|
||||
'I93' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96-300 pax', 'wtc' => 'H'],
|
||||
'I9F' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96 Freighters', 'wtc' => 'H'],
|
||||
'I9M' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96M pax', 'wtc' => 'H'],
|
||||
'I9X' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96-300 Freighter', 'wtc' => 'H'],
|
||||
'I9Y' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96T Freighter', 'wtc' => 'H'],
|
||||
'IL6' => ['icao' => 'IL62', 'description' => 'Ilyushin IL62', 'wtc' => 'H'],
|
||||
'IL7' => ['icao' => 'IL76', 'description' => 'Ilyushin IL76', 'wtc' => 'H'],
|
||||
'IL8' => ['icao' => 'IL18', 'description' => 'Ilyushin IL18', 'wtc' => 'M'],
|
||||
'IL9' => ['icao' => 'IL96', 'description' => 'Ilyushin IL96 pax', 'wtc' => 'H'],
|
||||
'ILW' => ['icao' => 'IL86', 'description' => 'Ilyushin IL86', 'wtc' => 'H'],
|
||||
'J31' => ['icao' => 'JS31', 'description' => 'British Aerospace Jetstream 31', 'wtc' => 'L'],
|
||||
'J32' => ['icao' => 'JS32', 'description' => 'British Aerospace Jetstream 32', 'wtc' => 'L'],
|
||||
'J41' => ['icao' => 'JS41', 'description' => 'British Aerospace Jetstream 41', 'wtc' => 'M'],
|
||||
'JST' => ['icao' => 'n/a', 'description' => 'British Aerospace Jetstream 31 / 32 / 41', 'wtc' => 'L/M'],
|
||||
'JU5' => ['icao' => 'JU52', 'description' => 'Junkers Ju52/3M', 'wtc' => 'M'],
|
||||
'L10' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 Tristar pax', 'wtc' => 'H'],
|
||||
'L11' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 1 / 50 / 100 / 150 / 200 / 250 Tristar pax', 'wtc' => 'H'],
|
||||
'L15' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 500 Tristar pax', 'wtc' => 'H'],
|
||||
'L1F' => ['icao' => 'L101', 'description' => 'Lockheed L-1011 Tristar Freighter', 'wtc' => 'H'],
|
||||
'L49' => ['icao' => 'CONI', 'description' => 'Lockheed L-1049 Super Constellation', 'wtc' => 'M'],
|
||||
'L4T' => ['icao' => 'L410', 'description' => 'LET 410', 'wtc' => 'L'],
|
||||
'LCH' => ['icao' => 'n/a', 'description' => 'Launch - Boat', 'wtc' => 'n/a'],
|
||||
'LMO' => ['icao' => 'n/a', 'description' => 'Limousine', 'wtc' => 'n/a'],
|
||||
'LOE' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra pax', 'wtc' => 'M'],
|
||||
'LOF' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra Freighter', 'wtc' => 'M'],
|
||||
'LOH' => ['icao' => 'C130', 'description' => 'Lockheed L-182 / 282 / 382 (L-100) Hercules', 'wtc' => 'M'],
|
||||
'LOM' => ['icao' => 'L188', 'description' => 'Lockheed L-188 Electra Mixed Configuration', 'wtc' => 'M'],
|
||||
'LRJ' => ['icao' => 'n/a', 'description' => 'Gates Learjet', 'wtc' => 'M'],
|
||||
'M11' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 pax', 'wtc' => 'H'],
|
||||
'M1F' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 Freighter', 'wtc' => 'H'],
|
||||
'M1M' => ['icao' => 'MD11', 'description' => 'McDonnell Douglas MD11 Mixed Configuration', 'wtc' => 'H'],
|
||||
'M80' => ['icao' => 'MD80', 'description' => 'McDonnell Douglas MD80', 'wtc' => 'M'],
|
||||
'M81' => ['icao' => 'MD81', 'description' => 'McDonnell Douglas MD81', 'wtc' => 'M'],
|
||||
'M82' => ['icao' => 'MD82', 'description' => 'McDonnell Douglas MD82', 'wtc' => 'M'],
|
||||
'M83' => ['icao' => 'MD83', 'description' => 'McDonnell Douglas MD83', 'wtc' => 'M'],
|
||||
'M87' => ['icao' => 'MD87', 'description' => 'McDonnell Douglas MD87', 'wtc' => 'M'],
|
||||
'M88' => ['icao' => 'MD88', 'description' => 'McDonnell Douglas MD88', 'wtc' => 'M'],
|
||||
'M90' => ['icao' => 'MD90', 'description' => 'McDonnell Douglas MD90', 'wtc' => 'M'],
|
||||
'MBH' => ['icao' => 'B105', 'description' => 'Eurocopter (MBB) Bo.105', 'wtc' => 'n/a'],
|
||||
'MD9' => ['icao' => 'EXPL', 'description' => 'MD Helicopters MD900 Explorer', 'wtc' => 'n/a'],
|
||||
'MIH' => ['icao' => 'MI8', 'description' => 'MIL Mi-8 / Mi-17 / Mi-171 / Mil-172', 'wtc' => 'n/a'],
|
||||
'MU2' => ['icao' => 'MU2', 'description' => 'Mitsubishi Mu-2', 'wtc' => 'L'],
|
||||
'ND2' => ['icao' => 'N262', 'description' => 'Aerospatiale (Nord) 262', 'wtc' => 'M'],
|
||||
'NDC' => ['icao' => 'S601', 'description' => 'Aerospatiale SN.601 Corvette', 'wtc' => 'L'],
|
||||
'NDE' => ['icao' => 'n/a', 'description' => 'Eurocopter (Aerospatiale) AS350 Ecureuil / AS355 Ecureuil 2', 'wtc' => 'n/a'],
|
||||
'NDH' => ['icao' => 'S65C', 'description' => 'Eurocopter (Aerospatiale) SA365C / SA365N Dauphin 2', 'wtc' => 'n/a'],
|
||||
'PA1' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - single piston engine', 'wtc' => 'L'],
|
||||
'PA2' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - twin piston engines', 'wtc' => 'L'],
|
||||
'PAG' => ['icao' => 'n/a', 'description' => 'Piper light aircraft', 'wtc' => 'L'],
|
||||
'PAT' => ['icao' => 'n/a', 'description' => 'Piper light aircraft - twin turboprop engines', 'wtc' => 'L'],
|
||||
'PL2' => ['icao' => 'PC12', 'description' => 'Pilatus PC-12', 'wtc' => 'L'],
|
||||
'PL6' => ['icao' => 'PC6T', 'description' => 'Pilatus PC-6 Turbo Porter', 'wtc' => 'L'],
|
||||
'PN6' => ['icao' => 'P68', 'description' => 'Partenavia P.68', 'wtc' => 'L'],
|
||||
'RFS' => ['icao' => 'n/a', 'description' => 'Road Feeder Service - Cargo Truck', 'wtc' => 'n/a'],
|
||||
'S20' => ['icao' => 'SB20', 'description' => 'Saab 2000', 'wtc' => 'M'],
|
||||
'S58' => ['icao' => 'S58T', 'description' => 'Sikorsky S-58T', 'wtc' => 'n/a'],
|
||||
'S61' => ['icao' => 'S61', 'description' => 'Sikorsky S-61', 'wtc' => 'n/a'],
|
||||
'S76' => ['icao' => 'S76', 'description' => 'Sikorsky S-76', 'wtc' => 'n/a'],
|
||||
'SF3' => ['icao' => 'SF34', 'description' => 'Saab SF340', 'wtc' => 'M'],
|
||||
'SFB' => ['icao' => 'SF34', 'description' => 'Saab SF340B', 'wtc' => 'M'],
|
||||
'SFF' => ['icao' => 'SF34', 'description' => 'Saab SF340 Freighter', 'wtc' => 'M'],
|
||||
'SH3' => ['icao' => 'SH33', 'description' => 'Shorts SD.330', 'wtc' => 'M'],
|
||||
'SH6' => ['icao' => 'SH36', 'description' => 'Shorts SD.360', 'wtc' => 'M'],
|
||||
'SHB' => ['icao' => 'BELF', 'description' => 'Shorts SC-5 Belfast', 'wtc' => 'M'],
|
||||
'SHS' => ['icao' => 'SC7', 'description' => 'Shorts SC-7 Skyvan', 'wtc' => 'L'],
|
||||
'SSC' => ['icao' => 'CONC', 'description' => 'Aerospatiale/BAC Concorde', 'wtc' => 'H'],
|
||||
'SU1' => ['icao' => '', 'description' => 'Sukhoi Superjet 100', 'wtc' => 'M'],
|
||||
'SU7' => ['icao' => '', 'description' => 'Sukhoi Superjet 100-75', 'wtc' => 'M'],
|
||||
'SU9' => ['icao' => 'SU95', 'description' => 'Sukhoi Superjet 100-95', 'wtc' => 'M'],
|
||||
'SWM' => ['icao' => 'n/a', 'description' => 'Fairchild (Swearingen) SA26 / SA226 / SA227 Metro / Merlin / Expediter', 'wtc' => 'L'],
|
||||
'T20' => ['icao' => 'T204', 'description' => 'Tupolev Tu-204 / Tu-214', 'wtc' => 'M'],
|
||||
'T2F' => ['icao' => 'T204', 'description' => 'Tupolev Tu-204 Freighter', 'wtc' => 'M'],
|
||||
'TRN' => ['icao' => 'n/a', 'description' => 'Train', 'wtc' => 'n/a'],
|
||||
'TU3' => ['icao' => 'T134', 'description' => 'Tupolev Tu134', 'wtc' => 'M'],
|
||||
'TU5' => ['icao' => 'T154', 'description' => 'Tupolev Tu154', 'wtc' => 'M'],
|
||||
'VCV' => ['icao' => 'VISC', 'description' => 'Vickers Viscount', 'wtc' => 'M'],
|
||||
'WWP' => ['icao' => 'WW24', 'description' => 'Israel Aircraft Industries 1124 Westwind', 'wtc' => 'M'],
|
||||
'YK2' => ['icao' => 'YK42', 'description' => 'Yakovlev Yak 42', 'wtc' => 'M'],
|
||||
'YK4' => ['icao' => 'YK40', 'description' => 'Yakovlev Yak 40', 'wtc' => 'M'],
|
||||
'YN2' => ['icao' => 'Y12', 'description' => 'Harbin Yunshuji Y12', 'wtc' => 'M'],
|
||||
'YN7' => ['icao' => 'AN24', 'description' => 'Xian Yunshuji Y7', 'wtc' => 'M'],
|
||||
'YS1' => ['icao' => 'YS11', 'description' => 'NAMC YS-11', 'wtc' => 'M'],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('aircraft', function (Blueprint $table) {
|
||||
$table->dropColumn('iata_code');
|
||||
});
|
||||
|
||||
Schema::create('iata_equipment_codes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('iata_code', 10)->unique();
|
||||
$table->string('icao_code', 10)->index();
|
||||
$table->string('description')->nullable();
|
||||
});
|
||||
|
||||
foreach ($this->aircraft_codes as $iata => $data) {
|
||||
$icao = $data['icao'];
|
||||
|
||||
if (empty($icao) || $icao === 'n/a') {
|
||||
continue;
|
||||
}
|
||||
|
||||
IataEquipmentCode::create([
|
||||
'iata_code' => $iata,
|
||||
'icao_code' => $icao,
|
||||
'description' => $data['description'],
|
||||
]);
|
||||
}
|
||||
|
||||
$extras = [
|
||||
// Boeing 737 NG — Scimitar Winglets
|
||||
'7S7' => ['icao' => 'B737', 'description' => 'Boeing 737-700 (Scimitar Winglets)'],
|
||||
'7S8' => ['icao' => 'B738', 'description' => 'Boeing 737-800 (Scimitar Winglets)'],
|
||||
'7S9' => ['icao' => 'B739', 'description' => 'Boeing 737-900 (Scimitar Winglets)'],
|
||||
|
||||
// Boeing 737 NG — Freighter/combi variants
|
||||
'73K' => ['icao' => 'B738', 'description' => 'Boeing 737-800 Freighter (Winglets)'],
|
||||
'73S' => ['icao' => 'B737', 'description' => 'Boeing 737-700 Freighter'],
|
||||
'73T' => ['icao' => 'B737', 'description' => 'Boeing 737-700 Freighter (Winglets)'],
|
||||
'73U' => ['icao' => 'B738', 'description' => 'Boeing 737-800 Freighter'],
|
||||
'7F8' => ['icao' => 'B738', 'description' => 'Boeing 737-800 Freighter (Scimitar Winglets)'],
|
||||
|
||||
// Boeing 737 MAX — alternate code from slot coordination data
|
||||
'7M1' => ['icao' => 'B3XM', 'description' => 'Boeing 737 MAX 10'],
|
||||
|
||||
// Boeing 757 — Freighter variants
|
||||
'75C' => ['icao' => 'B752', 'description' => 'Boeing 757-200 Freighter'],
|
||||
'75V' => ['icao' => 'B752', 'description' => 'Boeing 757-200 Freighter (Winglets)'],
|
||||
|
||||
// Airbus A318/319/321 — additional sharklet codes
|
||||
'31A' => ['icao' => 'A318', 'description' => 'Airbus A318 (Sharklets, alternate code)'],
|
||||
'31B' => ['icao' => 'A319', 'description' => 'Airbus A319 (Sharklets, alternate code)'],
|
||||
'32B' => ['icao' => 'A321', 'description' => 'Airbus A321 (Sharklets)'],
|
||||
|
||||
// Airbus A320 family — Freighter variants
|
||||
'32F' => ['icao' => 'A320', 'description' => 'Airbus A320 Freighter'],
|
||||
'32X' => ['icao' => 'A321', 'description' => 'Airbus A321 Freighter'],
|
||||
|
||||
// Airbus A330 — additional variants
|
||||
'33B' => ['icao' => 'A332', 'description' => 'Airbus A330-700 Beluga XL Freighter'],
|
||||
'33Y' => ['icao' => 'A333', 'description' => 'Airbus A330-300 Freighter'],
|
||||
|
||||
// Embraer — Enhanced Winglets
|
||||
'E7W' => ['icao' => 'E75L', 'description' => 'Embraer 175 (Enhanced Winglets)'],
|
||||
|
||||
// Sukhoi Superjet — Saberlets
|
||||
'S9S' => ['icao' => 'SU95', 'description' => 'Sukhoi Superjet 100-95 (Saberlets)'],
|
||||
|
||||
// COMAC C919 — alternate code
|
||||
'C19' => ['icao' => 'C919', 'description' => 'COMAC C919 (alternate IATA code)'],
|
||||
];
|
||||
|
||||
foreach ($extras as $iata => $data) {
|
||||
IataEquipmentCode::create([
|
||||
'iata_code' => $iata,
|
||||
'icao_code' => $data['icao'],
|
||||
'description' => $data['description'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Achievement;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('achievements', function (Blueprint $table) {
|
||||
$table->boolean('has_page')->default(false);
|
||||
});
|
||||
|
||||
$achievements = [
|
||||
'airlines_alliances.all_skyteam',
|
||||
'airlines_alliances.all_oneworld',
|
||||
'airlines_alliances.all_star_alliance',
|
||||
'airlines_alliances.all_vanilla_alliance',
|
||||
'fun_challenges.airline_alphabet',
|
||||
'fun_challenges.airport_alphabet',
|
||||
'fun_challenges.brazilian_states',
|
||||
'fun_challenges.us_states',
|
||||
'fun_challenges.australian_states',
|
||||
'fun_challenges.chinese_provinces',
|
||||
'fun_challenges.canadian_provinces',
|
||||
'countries_continents.all_continent_pairs_one_way',
|
||||
'countries_continents.all_continent_pairs_both_ways',
|
||||
];
|
||||
|
||||
foreach($achievements as $achievement) {
|
||||
Achievement::where('internal_name', $achievement)->update(['has_page' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Achievement;
|
||||
use App\Models\AchievementCategory;
|
||||
use App\Models\AchievementDifficulty;
|
||||
use App\Models\Airline;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
|
||||
Achievement::create([
|
||||
'name' => 'Circumnavigator',
|
||||
'internal_name' => 'general_flying.circumference_of_the_earth',
|
||||
'short_description' => 'Fly the same distance as the circumference of the Earth at the equator!',
|
||||
'icon' => 'standard_achievement.png',
|
||||
'progressive' => true,
|
||||
'long_description' => '',
|
||||
'achievement_category_id' => AchievementCategory::where('internal_name', 'general_flying')->first()->id,
|
||||
'achievement_difficulty_id' => AchievementDifficulty::where('internal_name', 'moderate')->first()->id,
|
||||
'threshold' => 40075,
|
||||
'has_page' => false,
|
||||
]);
|
||||
|
||||
Achievement::create([
|
||||
'name' => 'Fly Me to The Moon',
|
||||
'internal_name' => 'general_flying.to_the_moon',
|
||||
'short_description' => 'Fly the same distance as the Earth to the Moon!',
|
||||
'icon' => 'standard_achievement.png',
|
||||
'long_description' => '',
|
||||
'progressive' => true,
|
||||
'threshold' => 384400,
|
||||
'achievement_category_id' => AchievementCategory::where('internal_name', 'general_flying')->first()->id,
|
||||
'achievement_difficulty_id' => AchievementDifficulty::where('internal_name', 'hard')->first()->id,
|
||||
'has_page' => false,
|
||||
]);
|
||||
|
||||
Achievement::whereInternalName('aircraft.all_boeing_7x7')->update(['has_page' => true]);
|
||||
Achievement::whereInternalName('aircraft.all_airbus_a3xx')->update(['has_page' => true]);
|
||||
Airline::whereInternalName('south-africa-airways')->update(['name' => 'South African Airways']);
|
||||
|
||||
Schema::table('achievements', function (Blueprint $table) {
|
||||
$table->unsignedInteger('sort_order')->nullable()->after('id');
|
||||
});
|
||||
|
||||
// Seed sort_order from current id order, scoped per category
|
||||
$achievements = DB::table('achievements')
|
||||
->orderBy('achievement_category_id')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
$position = 1;
|
||||
$currentCategory = null;
|
||||
|
||||
foreach ($achievements as $achievement) {
|
||||
if ($achievement->achievement_category_id !== $currentCategory) {
|
||||
$position = 1;
|
||||
$currentCategory = $achievement->achievement_category_id;
|
||||
}
|
||||
DB::table('achievements')
|
||||
->where('id', $achievement->id)
|
||||
->update(['sort_order' => $position++]);
|
||||
}
|
||||
|
||||
// Move "Four on the Floor" (id 30) after "Triple Threat" (id 34)
|
||||
// within the aircraft category — swap their sort_order values
|
||||
$triEngine = DB::table('achievements')->where('internal_name', 'aircraft.tri_engine')->first();
|
||||
$quadEngine = DB::table('achievements')->where('internal_name', 'aircraft.quad_engine')->first();
|
||||
|
||||
DB::table('achievements')->where('internal_name', 'aircraft.quad_engine')
|
||||
->update(['sort_order' => $triEngine->sort_order + 1]);
|
||||
|
||||
// Shift everything between them up by 1 to make room
|
||||
DB::table('achievements')
|
||||
->where('achievement_category_id', $quadEngine->achievement_category_id)
|
||||
->where('sort_order', '>=', $triEngine->sort_order + 1)
|
||||
->where('internal_name', '!=', 'aircraft.quad_engine')
|
||||
->increment('sort_order');
|
||||
|
||||
$users = User::all();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->calculateAchievements();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Achievement;
|
||||
use App\Models\AchievementCategory;
|
||||
use App\Models\AchievementDifficulty;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\IataEquipmentCode;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
|
||||
function setDesignatorDefault($designator, $model_name, $manufacturer_code=null): self
|
||||
{
|
||||
$manufacturerSearchTerm = $manufacturer_code ? ['manufacturer_code' => $manufacturer_code] : [];
|
||||
|
||||
$count = Aircraft::where([
|
||||
'designator' => $designator,
|
||||
'model_full_name' => $model_name,
|
||||
...$manufacturerSearchTerm,
|
||||
])->update(['preferred' => true]);
|
||||
|
||||
echo $designator . ' ' . $model_name . ' ' . $manufacturer_code . ' ' . $count . PHP_EOL;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
function setDesignatorDefaults(): void
|
||||
{
|
||||
$this
|
||||
->setDesignatorDefault('A19N', 'A-319neo')
|
||||
->setDesignatorDefault('A306', 'A-300B4-600')
|
||||
->setDesignatorDefault('A30B', 'A-300B4-200')
|
||||
->setDesignatorDefault('A310', 'A-310')
|
||||
->setDesignatorDefault('A318', 'A-318')
|
||||
->setDesignatorDefault('A319', 'A-319')
|
||||
->setDesignatorDefault('A320', 'A-320')
|
||||
->setDesignatorDefault('A332', 'A-330-200')
|
||||
->setDesignatorDefault('A338', 'A-330-800')
|
||||
->setDesignatorDefault('A339', 'A-330-900')
|
||||
->setDesignatorDefault('A342', 'A-340-200')
|
||||
->setDesignatorDefault('A343', 'A-340-300')
|
||||
->setDesignatorDefault('A345', 'A-340-500')
|
||||
->setDesignatorDefault('A346', 'A-340-600')
|
||||
->setDesignatorDefault('A359', 'A-350-900 XWB')
|
||||
->setDesignatorDefault('A35K', 'A-350-1000 XWB')
|
||||
->setDesignatorDefault('A388', 'A-380-800')
|
||||
->setDesignatorDefault('AN24', 'An-24')
|
||||
->setDesignatorDefault('AN26', 'An-26')
|
||||
->setDesignatorDefault('AT43', 'ATR-42-300')
|
||||
->setDesignatorDefault('AT44', 'ATR-42-400')
|
||||
->setDesignatorDefault('AT72', 'ATR-72-201')
|
||||
->setDesignatorDefault('AT73', 'ATR-72-211')
|
||||
->setDesignatorDefault('AT75', 'ATR-72-500')
|
||||
->setDesignatorDefault('AT76', 'ATR-72-600')
|
||||
->setDesignatorDefault('B37M', '737 MAX 7')
|
||||
->setDesignatorDefault('B38M', '737 MAX 8')
|
||||
->setDesignatorDefault('B39M', '737 MAX 9')
|
||||
->setDesignatorDefault('B3XM', '737 MAX 10')
|
||||
->setDesignatorDefault('B461', 'BAe-146-100')
|
||||
->setDesignatorDefault('B462', 'BAe-146-200')
|
||||
->setDesignatorDefault('B703', '707-300')
|
||||
->setDesignatorDefault('B712', '717-200')
|
||||
->setDesignatorDefault('B721', '727-100')
|
||||
->setDesignatorDefault('B732', '737-200')
|
||||
->setDesignatorDefault('B737', '737-700')
|
||||
->setDesignatorDefault('B738', '737-800')
|
||||
->setDesignatorDefault('B739', '737-900')
|
||||
->setDesignatorDefault('B742', '747-200')
|
||||
->setDesignatorDefault('B748', '747-8')
|
||||
->setDesignatorDefault('B752', '757-200')
|
||||
->setDesignatorDefault('B772', '777-200')
|
||||
->setDesignatorDefault('B778', '777-8')
|
||||
->setDesignatorDefault('B77L', '777-200LR')
|
||||
->setDesignatorDefault('B77W', '777-300ER')
|
||||
->setDesignatorDefault('B788', '787-8 Dreamliner')
|
||||
->setDesignatorDefault('B789', '787-9 Dreamliner')
|
||||
->setDesignatorDefault('BCS1', 'A-220-100')
|
||||
->setDesignatorDefault('BCS3', 'A-220-300')
|
||||
->setDesignatorDefault('CRJ1', 'CL-600 Regional Jet CRJ-100')
|
||||
->setDesignatorDefault('CRJ2', 'CL-600 Regional Jet CRJ-200')
|
||||
->setDesignatorDefault('CRJ7', 'CL-600 Regional Jet CRJ-700')
|
||||
->setDesignatorDefault('CRJ9', 'CL-600 Regional Jet CRJ-900')
|
||||
->setDesignatorDefault('DC10', 'DC-10')
|
||||
->setDesignatorDefault('DC3', 'DC-3')
|
||||
->setDesignatorDefault('DH8B', 'DHC-8-200 Dash 8' , 'DE HAVILLAND CANADA')
|
||||
->setDesignatorDefault('DH8A', 'DHC-8-100 Dash 8' , 'DE HAVILLAND CANADA')
|
||||
->setDesignatorDefault('DH8C', 'DHC-8-300 Dash 8' , 'DE HAVILLAND CANADA')
|
||||
->setDesignatorDefault('DH8D', 'DHC-8-400 Dash 8' , 'DE HAVILLAND CANADA')
|
||||
->setDesignatorDefault('DHC6', 'DHC-6 Twin Otter','DE HAVILLAND CANADA')
|
||||
->setDesignatorDefault('E120', 'EMB-120 Brasilia', 'EMBRAER')
|
||||
->setDesignatorDefault('E135', 'ERJ-135')
|
||||
->setDesignatorDefault('E145', 'ERJ-145ER')
|
||||
->setDesignatorDefault('E170', '170')
|
||||
->setDesignatorDefault('E190', '190')
|
||||
->setDesignatorDefault('E195', '195')
|
||||
->setDesignatorDefault('E275', 'E175-E2')
|
||||
->setDesignatorDefault('E290', 'E190-E2')
|
||||
->setDesignatorDefault('E295', 'E195-E2')
|
||||
->setDesignatorDefault('F27', 'F-27 Friendship', 'FOKKER')
|
||||
->setDesignatorDefault('JS32', 'BAe-3200 Jetstream Super 31', 'BRITISH AEROSPACE')
|
||||
->setDesignatorDefault('JS41', 'BAe-4100 Jetstream 41', 'BRITISH AEROSPACE')
|
||||
->setDesignatorDefault('MD81', 'MD-81', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('MD82', 'MD-82', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('MD83', 'MD-83', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('MD87', 'MD-87', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('MD88', 'MD-88', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('MD90', 'MD-90', 'MCDONNELL DOUGLAS')
|
||||
->setDesignatorDefault('AJ27', 'C-909')
|
||||
;
|
||||
}
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('aircraft', function (Blueprint $table) {
|
||||
$table->boolean('preferred')->default(false)->after('designator');
|
||||
});
|
||||
|
||||
$this->setDesignatorDefaults();
|
||||
|
||||
IataEquipmentCode::create([
|
||||
'iata_code' => '388',
|
||||
'icao_code' => 'A388',
|
||||
'description' => 'Airbus A380'
|
||||
]);
|
||||
|
||||
Achievement::create([
|
||||
'name' => 'Gone a Gigametre',
|
||||
'internal_name' => 'general_flying.gigametre',
|
||||
'short_description' => 'Fly 1 million kilometres.',
|
||||
'long_description' => '',
|
||||
'achievement_difficulty_id' => AchievementDifficulty::whereInternalName('near_impossible')->first()->id,
|
||||
'achievement_category_id' => AchievementCategory::whereInternalName('general_flying')->first()->id,
|
||||
'icon' => 'standard_achievement.png',
|
||||
'has_page' => false,
|
||||
'sort_order' => 18,
|
||||
'progressive' => true,
|
||||
'threshold' => 1000000,
|
||||
]);
|
||||
|
||||
Achievement::whereInternalName('aircraft.smaller_manufacturer')->update([
|
||||
'difficulty_description' => 'General Aviation flights do not count. Only flights with an airline and flight number can earn this achievement',
|
||||
]);
|
||||
|
||||
foreach(User::all() as $user) {
|
||||
$user->calculateAchievements();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Airline;
|
||||
use App\Models\Country;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Airline::create([
|
||||
'name' => 'Scoot',
|
||||
'IATA_code' => 'TR',
|
||||
'ICAO_code' => 'TGW',
|
||||
'internal_name' => 'scoot-new',
|
||||
'logo' => 'TR.png',
|
||||
'active' => true,
|
||||
'country_id' => Country::whereCode('SG')->first()->id,
|
||||
]);
|
||||
|
||||
Aircraft::where('manufacturer_code', 'ATR')
|
||||
->each(function ($aircraft) {
|
||||
$aircraft->update([
|
||||
'model_full_name' => str_replace('ATR-', '', $aircraft->model_full_name),
|
||||
]);
|
||||
});
|
||||
|
||||
Aircraft::where('manufacturer_code', 'AIRBUS')
|
||||
->each(function ($aircraft) {
|
||||
$aircraft->update([
|
||||
'model_full_name' => str_replace('A-', 'A', $aircraft->model_full_name),
|
||||
]);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Models\FlightReason;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
FlightReason::where('name', 'Other')->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Airline;
|
||||
use App\Models\Country;
|
||||
use App\Models\UserFlight;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Airline::create([
|
||||
'name' => 'Pacific Blue',
|
||||
'internal_name' => 'pacific-blue',
|
||||
'IATA_code' => 'DJ',
|
||||
'ICAO_code' => 'PBN',
|
||||
'active' => false,
|
||||
'logo' => 'pacific-blue.png',
|
||||
'country_id' => Country::where('code', 'NZ')->first()->id,
|
||||
]);
|
||||
|
||||
$flightIds = [326, 327];
|
||||
|
||||
UserFlight::whereIn('id', $flightIds)->update(['airline_id' => Airline::where('internal_name', 'pacific-blue')->first()->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('distance_unit', 3)->default('km')->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('airlines', function (Blueprint $table) {
|
||||
$table->renameColumn('IATA_code', 'iata_code');
|
||||
$table->renameColumn('ICAO_code', 'icao_code');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
$table->id(); // permission id
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
$table->id(); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$role = Role::create(['name' => 'admin']);
|
||||
$permission = Permission::create(['name' => 'reconcile_missing_liveries']);
|
||||
|
||||
$role->givePermissionTo($permission);
|
||||
|
||||
$user = User::whereName('Josh')->first();
|
||||
|
||||
$user->assignRole($role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ignored_missing_liveries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('filename')->unique()->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ignored_missing_liveries');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
// database/migrations/xxxx_add_settings_to_users_table.php
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->json('settings')->nullable()->after('password');
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('followees', function (Blueprint $table) {
|
||||
$table->boolean('verified')->default(true)->after('followee_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -23,7 +23,7 @@ class AirlinesSeeder extends Seeder
|
||||
$renames = [
|
||||
'airlineId' => 'id',
|
||||
'codeIataAirline' => 'IATA_code',
|
||||
'codeIcaoAirline' => 'ICAO_code',
|
||||
'codeIcaoAirline' => 'icao_code',
|
||||
'slug' => 'internal_name',
|
||||
'nameAirline' => 'name',
|
||||
'codeIso2Country' => 'country_code',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@inertiajs/vue3": "^2.0.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
@@ -35,6 +36,6 @@
|
||||
"maplibre-gl": "^5.22.0",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue3-apexcharts": "^1.11.1",
|
||||
"vuetify": "^4.0.5"
|
||||
"vuetify": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
|
||||
<path d="M0 0 C2.9375 1.1875 2.9375 1.1875 4.75 4.4375 C9.79823035 20.37928005 6.72721297 37.49490904 0.31640625 52.6015625 C-1.1920586 55.43046825 -2.98958993 57.74930004 -5.0625 60.1875 C-8.86859809 58.7179003 -10.2356926 56.80258531 -12.25 53.3125 C-13.01376953 52.01699219 -13.01376953 52.01699219 -13.79296875 50.6953125 C-18.9892567 40.43064523 -16.71207847 25.69760882 -13.26953125 15.1875 C-11.63832098 11.13322983 -9.74682264 7.63137374 -7.0625 4.1875 C-6.423125 3.341875 -5.78375 2.49625 -5.125 1.625 C-3.0625 0.1875 -3.0625 0.1875 0 0 Z " fill="#008AC1" transform="translate(68.0625,3.8125)"/>
|
||||
<path d="M0 0 C0.74765625 0.42410156 1.4953125 0.84820312 2.265625 1.28515625 C8.1047672 4.85021075 13.25197139 9.22303858 17 15 C17.62536267 17.68856707 17.47051231 19.23574018 17 22 C11.51599695 24.74200153 5.73329795 22.80754733 0.25 21 C-9.6545213 17.23543355 -20.88844139 11.00556137 -27 2 C-27.203125 -0.73828125 -27.203125 -0.73828125 -27 -3 C-17.88529518 -5.76816961 -8.10184736 -4.88852068 0 0 Z " fill="#E86533" transform="translate(100,72)"/>
|
||||
<path d="M0 0 C-0.83163971 5.91388239 -3.65841913 9.06469551 -8 13 C-16.04118869 18.78732929 -26.35309977 20.55877329 -36.11328125 19.73828125 C-42.39874117 18.60125883 -42.39874117 18.60125883 -45 16 C-44.91170833 12.64491643 -44.32277092 11.32277092 -41.9375 8.9375 C-30.1667114 1.17378837 -14.01371745 -4.05914574 0 0 Z " fill="#04B2C4" transform="translate(51,66)"/>
|
||||
<path d="M0 0 C2.9375 1.1875 2.9375 1.1875 4.75 4.4375 C9.42068654 19.18703645 6.95887585 34.84383074 1.9375 49.1875 C1.16416788 46.86750365 0.88730164 45.4248787 1.3984375 43.0078125 C2.05925736 39.55047959 2.10728024 36.26914473 2.125 32.75 C2.14626953 30.83380859 2.14626953 30.83380859 2.16796875 28.87890625 C1.94356734 25.28468029 1.24278856 22.52553042 -0.0625 19.1875 C-2.90784204 18.89121263 -2.90784204 18.89121263 -6.0625 20.1875 C-10.87111423 27.19688212 -12.176804 34.12015788 -12.625 42.4375 C-12.66818359 43.18257812 -12.71136719 43.92765625 -12.75585938 44.6953125 C-12.86146725 46.525849 -12.96244201 48.35665176 -13.0625 50.1875 C-17.40081636 45.84918364 -16.36582483 39.17469119 -16.3815918 33.39697266 C-16.21731375 22.78779035 -13.68360918 12.68210632 -7.0625 4.1875 C-6.423125 3.341875 -5.78375 2.49625 -5.125 1.625 C-3.0625 0.1875 -3.0625 0.1875 0 0 Z " fill="#39A6D4" transform="translate(68.0625,3.8125)"/>
|
||||
<path d="M0 0 C0.33 0.33 0.66 0.66 1 1 C0.84984669 9.25843232 -2.60481663 15.23535952 -8.08203125 21.30859375 C-13.72461166 26.69424405 -19.2219303 29.70746512 -27.0625 30.1875 C-27.701875 30.125625 -28.34125 30.06375 -29 30 C-30.31038168 26.06885495 -29.37989883 25.02867974 -27.5625 21.375 C-22.27720245 11.29152911 -12.82384281 -1.87014374 0 0 Z " fill="#633187" transform="translate(100,34)"/>
|
||||
<path d="M0 0 C5.93960036 -0.75824685 9.00354596 0.74419986 13.80078125 4.23828125 C21.18900343 10.15673277 27.59952269 17.19802259 29.875 26.625 C29.91625 27.40875 29.9575 28.1925 30 29 C25.27204456 30.44494818 21.58350797 29.69950295 17 28 C8.82293751 23.42685153 4.11457096 16.22914193 0 8 C-0.13415472 5.3276379 -0.04318541 2.67749512 0 0 Z " fill="#57A54A" transform="translate(23,32)"/>
|
||||
<path d="M0 0 C2.86880035 1.88824042 5.43872646 3.87745293 7 7 C7.56735892 11.65048294 7.4598631 15.09730577 5.3125 19.3125 C-0.12652788 25.6335324 -0.12652788 25.6335324 -3.95703125 26.3828125 C-7.26055142 26.5666819 -9.5828384 26.24601838 -12.4375 24.5625 C-15.07406785 20.23852873 -15.60234498 15.7648622 -14.5234375 10.8125 C-11.91798084 3.25381253 -8.54250005 -2.29990386 0 0 Z " fill="#F0AE49" transform="translate(64,66)"/>
|
||||
<path d="M0 0 C7.62760221 -0.71658058 12.84695266 0.32368402 19 5 C22.32385996 8.00860727 24.79551563 10.39820575 25.375 14.9375 C25.25125 15.618125 25.1275 16.29875 25 17 C17.26168246 17.89288279 11.38595099 15.35197521 5 11 C1.90389071 7.58204983 0 4.65967563 0 0 Z " fill="#DF3C2E" transform="translate(76,69)"/>
|
||||
<path d="M0 0 C-0.66386719 0.24234375 -1.32773437 0.4846875 -2.01171875 0.734375 C-9.11824782 3.35660009 -9.11824782 3.35660009 -15 8 C-15 9.32 -15 10.64 -15 12 C-7.83008688 15.58495656 3.87451725 12.9346496 11.12109375 10.65625 C11.74113281 10.4396875 12.36117187 10.223125 13 10 C7.94075638 15.78671656 -0.36338753 17.15878877 -7.6875 17.875 C-12.92104443 18.14290763 -17.06897905 17.76772449 -22 16 C-22.66 15.34 -23.32 14.68 -24 14 C-23.91170833 10.64491643 -23.32277092 9.32277092 -20.9375 6.9375 C-7.1842593 -2.13378642 -7.1842593 -2.13378642 0 0 Z " fill="#5EC4D8" transform="translate(30,68)"/>
|
||||
<path d="M0 0 C4.30954702 0.17238188 7.156053 0.69457019 10.3125 3.625 C14.1626862 7.89613989 15 12.33884306 15 18 C9.69453808 17.67845685 6.50165938 14.71781119 3 11 C0.58988665 7.19046599 -0.3975267 4.50530258 0 0 Z " fill="#018F4B" transform="translate(34,43)"/>
|
||||
<path d="M0 0 C0.763125 0.20625 1.52625 0.4125 2.3125 0.625 C2.04374573 7.07510247 -0.32163178 10.92172276 -4.6875 15.625 C-8.27533784 18.625 -8.27533784 18.625 -10.6875 18.625 C-10.43023307 16.72727809 -10.15606661 14.83184323 -9.875 12.9375 C-9.72417969 11.88175781 -9.57335937 10.82601562 -9.41796875 9.73828125 C-8.23699295 4.70492448 -6.01598249 -0.83943942 0 0 Z " fill="#403182" transform="translate(88.6875,43.375)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C2.59865471 6.98654709 2.59865471 6.98654709 0.95703125 9.765625 C-0.83858185 11.93510476 -2.46143928 13.73071964 -5 15 C-8 15.1875 -8 15.1875 -11 15 C-11.66 14.34 -12.32 13.68 -13 13 C-13 11.68 -13 10.36 -13 9 C-10.85242436 7.21822021 -8.83417915 5.73979833 -6.5 4.25 C-5.87996094 3.83878906 -5.25992188 3.42757812 -4.62109375 3.00390625 C-3.08882829 1.99025371 -1.54541883 0.99348354 0 0 Z " fill="#E68434" transform="translate(66,70)"/>
|
||||
<path d="M0 0 C0.99 0.66 1.98 1.32 3 2 C1.50610707 5.83658866 -0.63623019 7.46185426 -4 9.75 C-5.3303125 10.67039062 -5.3303125 10.67039062 -6.6875 11.609375 C-9 13 -9 13 -11 13 C-11.33 13.66 -11.66 14.32 -12 15 C-12.62739631 6.0247473 -12.62739631 6.0247473 -10.125 1.9375 C-6.48014483 -1.3857503 -4.73559018 -1.06309167 0 0 Z " fill="#E5A037" transform="translate(64,66)"/>
|
||||
<path d="M0 0 C3.3 0 6.6 0 10 0 C8.515 1.485 8.515 1.485 7 3 C7.66 4.65 8.32 6.3 9 8 C9.66 8 10.32 8 11 8 C12.0625 9.8125 12.0625 9.8125 13 12 C12.67 12.99 12.34 13.98 12 15 C6.04495399 11.86348305 2.75924728 9.20830637 0 3 C0 2.01 0 1.02 0 0 Z " fill="#DF4A2F" transform="translate(76,69)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.63016497 4.16115776 0.0109362 4.9927092 -3 7 C-3.70355115 9.57679229 -4.24517532 12.00489097 -4.6875 14.625 C-4.81705078 15.33140625 -4.94660156 16.0378125 -5.08007812 16.765625 C-5.39801563 18.50839354 -5.70049829 20.25396874 -6 22 C-7.65 22 -9.3 22 -11 22 C-12 19 -12 19 -10.99609375 16.328125 C-7.87593884 10.16601455 -4.7751598 5.00254836 0 0 Z " fill="#672F82" transform="translate(82,42)"/>
|
||||
<path d="M0 0 C0.99 0 1.98 0 3 0 C3.24492188 0.65226562 3.48984375 1.30453125 3.7421875 1.9765625 C6.57675185 8.60357847 9.43399165 11.54643077 15.890625 14.74609375 C18 16 18 16 19 19 C11.54445969 16.67014365 4.98955628 10.86272863 0 5 C-0.375 2.125 -0.375 2.125 0 0 Z " fill="#E88D38" transform="translate(73,69)"/>
|
||||
<path d="M0 0 C2.75 0.9375 2.75 0.9375 3.75 2.9375 C1.96190303 4.10963045 0.16940875 5.27505526 -1.625 6.4375 C-2.62273438 7.0871875 -3.62046875 7.736875 -4.6484375 8.40625 C-7.25 9.9375 -7.25 9.9375 -9.25 9.9375 C-8.95118318 6.45130377 -8.21472514 4.89113875 -6.0625 2.0625 C-3.25 -0.0625 -3.25 -0.0625 0 0 Z " fill="#E3BD38" transform="translate(61.25,65.0625)"/>
|
||||
<path d="M0 0 C0.99 0 1.98 0 3 0 C4.82421875 2.625 4.82421875 2.625 6.6875 6 C7.31011719 7.11375 7.93273437 8.2275 8.57421875 9.375 C9.04472656 10.24125 9.51523438 11.1075 10 12 C9.01 12.495 9.01 12.495 8 13 C4.72653393 9.90347804 2.02490044 7.04980088 0 3 C0 2.01 0 1.02 0 0 Z " fill="#DF5C30" transform="translate(76,69)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.43754389 4.83511953 -2.10365784 7.10365784 -5 10 C-6.29519995 12.16620544 -6.29519995 12.16620544 -7 14 C-7.66 14 -8.32 14 -9 14 C-7.65070536 10.16336928 -5.92248975 7.34827431 -3.375 4.1875 C-2.42753906 3.00220703 -2.42753906 3.00220703 -1.4609375 1.79296875 C-0.97882813 1.20128906 -0.49671875 0.60960937 0 0 Z " fill="#862D84" transform="translate(82,42)"/>
|
||||
<path d="M0 0 C0.99 0 1.98 0 3 0 C4.32 2.97 5.64 5.94 7 9 C1.95652174 7.73913043 1.95652174 7.73913043 0 5 C-0.1875 2.3125 -0.1875 2.3125 0 0 Z " fill="#E17C35" transform="translate(73,69)"/>
|
||||
<path d="M0 0 C3.98069267 2.87494471 6.43571997 5.82117329 9 10 C8.67 10.66 8.34 11.32 8 12 C6.6547417 10.76284282 5.32412124 9.50975423 4 8.25 C3.2575 7.55390625 2.515 6.8578125 1.75 6.140625 C0 4 0 4 0 0 Z " fill="#71AD4A" transform="translate(41,41)"/>
|
||||
<path d="M0 0 C0.66 0 1.32 0 2 0 C1.5520266 2.31452922 1.09133539 4.62660039 0.625 6.9375 C0.36976562 8.22527344 0.11453125 9.51304688 -0.1484375 10.83984375 C-0.42945312 11.88269531 -0.71046875 12.92554688 -1 14 C-1.99 14.495 -1.99 14.495 -3 15 C-2.44854729 9.76119922 -1.57077907 5.02649303 0 0 Z " fill="#543082" transform="translate(79,48)"/>
|
||||
<path d="M0 0 C3.90288685 2.60192457 3.99523245 4.51719093 5 9 C5 10.32 5 11.64 5 13 C4.01 12.67 3.02 12.34 2 12 C1.34 8.04 0.68 4.08 0 0 Z " fill="#25954A" transform="translate(44,48)"/>
|
||||
<path d="M0 0 C1.32 0.33 2.64 0.66 4 1 C-0.455 4.465 -0.455 4.465 -5 8 C-5 5 -5 5 -2.75 2.25 C-1.8425 1.5075 -0.935 0.765 0 0 Z " fill="#E6D13D" transform="translate(58,65)"/>
|
||||
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C1.67 3.31 1.34 5.62 1 8 C0.01 7.67 -0.98 7.34 -2 7 C-1.34 4.69 -0.68 2.38 0 0 Z " fill="#862D84" transform="translate(72,56)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 550 KiB |
|
Before Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 615 KiB |
|
Before Width: | Height: | Size: 639 KiB |
|
Before Width: | Height: | Size: 492 KiB |
|
Before Width: | Height: | Size: 524 KiB |
|
Before Width: | Height: | Size: 528 KiB |
|
Before Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 422 KiB |
|
Before Width: | Height: | Size: 563 KiB |
|
Before Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 464 KiB |
|
Before Width: | Height: | Size: 551 KiB |
|
Before Width: | Height: | Size: 543 KiB |
|
Before Width: | Height: | Size: 524 KiB |
|
Before Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 578 KiB |
|
Before Width: | Height: | Size: 503 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 535 KiB |
|
Before Width: | Height: | Size: 557 KiB |
|
Before Width: | Height: | Size: 537 KiB |