diff --git a/app/Console/Commands/PopulateAirportTimezones.php b/app/Console/Commands/PopulateAirportTimezones.php
index dd97396..d709afa 100644
--- a/app/Console/Commands/PopulateAirportTimezones.php
+++ b/app/Console/Commands/PopulateAirportTimezones.php
@@ -17,7 +17,10 @@ class PopulateAirportTimezones extends Command
*/
public function handle()
{
- Airport::whereNull('timezone')->chunkById(100, function ($airports) {
+ Airport::where(function ($query) {
+ $query->whereNull('timezone')
+ ->orWhere('timezone', 'Undefined');
+ })->chunkById(100, function ($airports) {
foreach ($airports as $airport) {
$zoneName = null;
$attempts = 0;
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index 490f167..6d53615 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -58,28 +58,38 @@ class SearchController extends Controller
{
$q = request('q', '');
$len = strlen($q);
+ $includeInactive = request()->boolean('include_inactive');
if ($len < 3) return [];
return Airport::with('region.country')
+ ->when(!$includeInactive, fn($query) => $query->where('active', true))
->when($len === 3, fn($query) => $query->where('iata_code', 'ilike', $q))
->when($len >= 4, fn($query) => $query->where(function ($sub) use ($q, $len) {
$sub->when($len === 4, fn($s) => $s->where('icao_code', 'ilike', $q))
->orWhere('name', 'ilike', "%{$q}%")
->orWhere('municipality', 'ilike', "%{$q}%");
- })->orderByRaw("
- CASE
- WHEN icao_code = ? THEN 0
- WHEN iata_code = ? THEN 1
- ELSE 2
- END
- ", [$q, $q]))
+ }))
+ ->orderByRaw("
+ CASE
+ WHEN active THEN 0
+ ELSE 1
+ END
+ ")
+ ->orderByRaw("
+ CASE
+ WHEN icao_code = ? THEN 0
+ WHEN iata_code = ? THEN 1
+ ELSE 2
+ END
+ ", [$q, $q])
->limit(15)
- ->get(['id', 'name', 'municipality', 'iata_code', 'icao_code', 'region_id'])
- ->map(fn($airport) => [
+ ->get(['id', 'name', 'municipality', 'iata_code', 'icao_code', 'region_id', 'active'])
+ ->map(fn(Airport $airport) => [
'value' => $airport->id,
'title' => $airport->display_name,
'country_code' => strtolower($airport->region->country->code),
+ 'active' => $airport->active,
])
->values();
}
diff --git a/app/Models/Airport.php b/app/Models/Airport.php
index 2327e03..967575b 100644
--- a/app/Models/Airport.php
+++ b/app/Models/Airport.php
@@ -20,12 +20,14 @@ class Airport extends Model
'icao_code',
'iata_code',
'local_code',
+ 'active',
];
protected $casts = [
'latitude_deg' => 'float',
'longitude_deg' => 'float',
'elevation_ft' => 'integer',
+ 'active' => 'boolean',
];
protected $appends = [
@@ -37,6 +39,8 @@ class Airport extends Model
'LHR', 'LGW', 'STN', 'LTN', 'LCY', 'SEN',
];
+
+
protected function displayName() : Attribute{
return Attribute::make(
get: function () {
@@ -53,6 +57,11 @@ class Airport extends Model
);
}
+ public function scopeActive($query)
+ {
+ return $query->where('active', true);
+ }
+
public function region(): BelongsTo
{
return $this->belongsTo(Region::class);
diff --git a/database/migrations/2026_08_22_031806_add_historic_airports.php b/database/migrations/2026_08_22_031806_add_historic_airports.php
new file mode 100644
index 0000000..61e065f
--- /dev/null
+++ b/database/migrations/2026_08_22_031806_add_historic_airports.php
@@ -0,0 +1,133 @@
+boolean('active')->default(true);
+ });
+
+ Airport::whereIataCode('TXL')->update(['active' => false]);
+ $this->importCsv();
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('airports', function (Blueprint $table) {
+ $table->dropColumn('active');
+ });
+ }
+
+ private function importCsv(): void
+ {
+ $path = storage_path('app/private/seed_data/airports.csv');
+
+ if (! file_exists($path)) {
+ throw new \RuntimeException("Airports CSV not found at: {$path}");
+ }
+
+ $handle = fopen($path, 'rb');
+
+ if ($handle === false) {
+ throw new \RuntimeException("Failed to open airports CSV at: {$path}");
+ }
+
+ // Skip header row
+ fgetcsv($handle);
+
+ $regionMap = DB::table('regions')->pluck('id', 'code')->all();
+
+ $batch = [];
+ $batchSize = 500;
+ $now = now()->toDateTimeString();
+
+ while (($row = fgetcsv($handle)) !== false) {
+ if (count($row) < 19) {
+ continue;
+ }
+
+ // id, ident, type, name, latitude_deg, longitude_deg, elevation_ft,
+ // continent, iso_country, iso_region, municipality, scheduled_service,
+ // icao_code, iata_code, gps_code, local_code, home_link, wikipedia_link, keywords
+ [
+ ,
+ ,
+ $type,
+ $name,
+ $latitudeDeg,
+ $longitudeDeg,
+ $elevationFt,
+ ,
+ ,
+ $isoRegion,
+ $municipality,
+ ,
+ $icaoCode,
+ $iataCode,
+ ,
+ $localCode,
+ ] = $row;
+
+ $icaoCode = trim(str_replace(["\r", "\n"], '', $icaoCode));
+ $iataCode = trim(str_replace(["\r", "\n"], '', $iataCode));
+
+ // Only importing airports with no IATA/ICAO code — coded airports are already in the DB
+ if ($icaoCode !== '' || $iataCode !== '') {
+ continue;
+ }
+
+ $type = trim($type);
+ $name = trim($name);
+ $localCode = trim($localCode);
+ $isoRegion = trim($isoRegion);
+
+ if (! isset($regionMap[$isoRegion])) {
+ continue;
+ }
+
+ $elevationFt = trim($elevationFt);
+
+ $batch[] = [
+ 'type' => $type,
+ 'name' => $name,
+ 'latitude_deg' => (float) trim($latitudeDeg),
+ 'longitude_deg' => (float) trim($longitudeDeg),
+ 'elevation_ft' => $elevationFt !== '' ? (int) $elevationFt : null,
+ 'region_id' => $regionMap[$isoRegion],
+ 'municipality' => trim($municipality) !== '' ? trim($municipality) : null,
+ 'icao_code' => null,
+ 'iata_code' => null,
+ 'local_code' => $localCode !== '' ? $localCode : null,
+ 'timezone' => 'Undefined',
+ 'active' => $type !== 'closed',
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ];
+
+ echo "Added: {$name}\n";
+
+ if (count($batch) >= $batchSize) {
+ DB::table('airports')->insert($batch);
+ $batch = [];
+ }
+ }
+
+ fclose($handle);
+
+ if (! empty($batch)) {
+ DB::table('airports')->insert($batch);
+ }
+ }
+};
diff --git a/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue b/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue
index 1894328..502eaff 100644
--- a/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue
+++ b/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue
@@ -1,5 +1,5 @@
@@ -62,7 +76,6 @@ const searchAirports = async (query: string) => {
-