Added Imported Flights Table

This commit is contained in:
2026-04-03 23:59:40 +10:00
parent 6a88d0cdfb
commit 877caa3291
16 changed files with 622 additions and 21 deletions
@@ -0,0 +1,192 @@
<?php
namespace App\Http\Controllers;
use App\Models\Airline;
use App\Models\ImportedFlight;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class FlightImportController extends Controller
{
const array FLIGHT_CLASSES = [
0 => 'Please Select',
1 => 'Economy',
2 => 'Business',
3 => 'First',
4 => 'Premium Economy',
5 => 'Private',
];
const array SEAT_TYPES = [
0 => 'Please Select',
1 => 'Window',
2 => 'Middle',
3 => 'Aisle',
];
const array FLIGHT_REASONS = [
0 => 'Please Select',
1 => 'Pleasure',
2 => 'Business',
3 => 'Crew',
4 => 'Other',
];
private function formatTime(?string $time): string
{
if (!$time) return '00:00';
[$hours, $minutes] = explode(':', $time);
return str_pad($hours, 2, '0', STR_PAD_LEFT) . ':' . $minutes;
}
public function getPossibleAirlines(string $airlineQuery) {
preg_match('/\((\w{2,3})\/(\w{3,4})\)/', $airlineQuery, $matches);
$iata = $matches[1] ?? null;
$icao = $matches[2] ?? null;
$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
ELSE 3
END
", [$iata, $icao, $iata, $icao])
->where(function ($q) use ($iata, $icao) {
$q->where('IATA_code', $iata)
->orWhere('ICAO_code', $icao);
});
})
->orderByDesc('active')
->limit(10)
->get(['id', 'name', 'IATA_code', 'ICAO_code'])
->map(fn($a) => [
'value' => $a->id,
'title' => "{$a->name} ({$a->IATA_code}/{$a->ICAO_code})",
])
->values()
->toArray();
return [
'airline_options' => $airlines,
'raw_airline' => $airlineQuery,
];
}
public function reconcile(Request $request)
{
$user = Auth::user();
$flightToReconcile = ImportedFlight::where('user_id', $user->id)->orderBy('id')->first();
if (!$flightToReconcile) {
return null;
}
$date = null;
if ($flightToReconcile->date) {
$date = Carbon::createFromFormat('m-d-y', $flightToReconcile->date)->format('Y-m-d');
}
return [
'flight_classes' => collect(self::FLIGHT_CLASSES)->map(fn($title, $value) => [
'value' => $value,
'title' => $title,
])->values(),
'flight_reasons' => collect(self::FLIGHT_REASONS)->map(fn($title, $value) => [
'value' => $value,
'title' => $title,
])->values(),
'seat_types' => collect(self::SEAT_TYPES)->map(fn($title, $value) => [
'value' => $value,
'title' => $title,
])->values(),
'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 ?? '')['airline_options'],
];
}
public function store(Request $request)
{
try {
$request->validate([
'csv' => ['required', 'file', 'mimes:csv,txt', 'max:10240'],
]);
$path = $request->file('csv')->getRealPath();
$handle = fopen($path, 'r');
fgetcsv($handle);
// Read and normalise header row
$headers = array_map(
fn($h) => strtolower(trim(str_replace(' ', '_', $h))),
fgetcsv($handle)
);
\Log::debug('CSV headers', $headers);
$firstRow = fgetcsv($handle);
\Log::debug('First row', $firstRow ?? ['empty']);
\Log::debug('Header count: ' . count($headers) . ', Row count: ' . count($firstRow ?? []));
$map = [
'date' => 'date',
'flight_number' => 'flight_number',
'from' => 'from',
'to' => 'to',
'dep_time' => 'dep_time',
'arr_time' => 'arr_time',
'duration' => 'duration',
'airline' => 'airline',
'aircraft' => 'aircraft',
'registration' => 'registration',
'seat_number' => 'seat_number',
'seat_type' => 'seat_type',
'flight_class' => 'flight_class',
'flight_reason' => 'flight_reason',
'note' => 'note',
];
$userId = Auth::id();
$imported = 0;
while (($row = fgetcsv($handle)) !== false) {
if (count($row) !== count($headers)) {
continue; // skip malformed rows
}
$raw = array_combine($headers, $row);
$data = ['user_id' => $userId];
foreach ($map as $csvKey => $column) {
$data[$column] = $raw[$csvKey] ?? null;
}
ImportedFlight::create($data);
$imported++;
}
fclose($handle);
return response()->json(['imported' => $imported]);
} catch (\Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
}
+7
View File
@@ -21,6 +21,13 @@ class LogoController extends Controller
]); ]);
} }
public function getLogoById(int $id){
$airline = Airline::where('id', $id)
->first();
return $this->getAirlineLogo($airline);
}
public function getLogoByCode(string $code){ public function getLogoByCode(string $code){
$column = strlen($code) == 2 $column = strlen($code) == 2
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ImportedFlight extends Model
{
public $timestamps = false;
protected $fillable = [
'user_id',
'date',
'flight_number',
'from',
'to',
'dep_time',
'arr_time',
'duration',
'airline',
'aircraft',
'registration',
'seat_number',
'seat_type',
'flight_class',
'flight_reason',
'note',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+7
View File
@@ -7,6 +7,8 @@ use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@@ -29,4 +31,9 @@ class User extends Authenticatable
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
public function ImportedFlights(): HasMany
{
return $this->hasMany(ImportedFlight::class);
}
} }
+7
View File
@@ -5,6 +5,7 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47",
"vuetify": "^4.0.5" "vuetify": "^4.0.5"
}, },
"devDependencies": { "devDependencies": {
@@ -174,6 +175,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@mdi/font": {
"version": "7.4.47",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz",
"integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==",
"license": "Apache-2.0"
},
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
+1
View File
@@ -26,6 +26,7 @@
"vue": "^3.4.0" "vue": "^3.4.0"
}, },
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47",
"vuetify": "^4.0.5" "vuetify": "^4.0.5"
} }
} }
+2 -3
View File
@@ -1,6 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root { :root {
--bg: #0a0f1c; /* deep night sky */ --bg: #0a0f1c; /* deep night sky */
@@ -12,6 +10,7 @@
--accent-soft: #0ea5e9; /* deeper blue */ --accent-soft: #0ea5e9; /* deeper blue */
--accent-glow: rgba(56, 189, 248, 0.15); --accent-glow: rgba(56, 189, 248, 0.15);
--border: #1f2937; --border: #1f2937;
} }
.glass { .glass {
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import axios from 'axios'
const props = defineProps<{
prefilledOptions: { value: number, title: string }[]
errorMessages?: string[] | string
}>()
const model = defineModel<{ value: number, title: string } | null>()
const airlineOptions = ref(props.prefilledOptions ?? [])
const autocompleteRef = ref<any>(null)
const onFocus = () => {
nextTick(() => {
const input = autocompleteRef.value?.$el?.querySelector('input')
input?.select()
})
}
const searchAirlines = async (query: string) => {
if (!query || query.length < 2) {
airlineOptions.value = props.prefilledOptions ?? []
return
}
if (query === model.value?.title) return
const { data } = await axios.get('/airlines/search', { params: { q: query } })
airlineOptions.value = data
}
</script>
<template>
<v-autocomplete
ref="autocompleteRef"
v-model="model"
label="Airline"
:items="airlineOptions"
:error-messages="errorMessages"
item-title="title"
@update:search="searchAirlines"
@focus="onFocus"
hint="Showing closest matches to the imported value"
placeholder="SriLankan Airlines"
clearable
hide-no-data
return-object
>
<template #prepend-inner>
<img
v-if="model"
style="padding: 0.25em"
width="40"
height="40"
:src="`http://api.flightsgoneby.test:8000/airlines/logos/tail/id/${model.value}`"
/>
</template>
<template #item="{ item, props: itemProps }">
<v-list-item v-bind="itemProps">
<template #prepend>
<img style="padding:0.25em" width="40" height="40" :src="`http://api.flightsgoneby.test:8000/airlines/logos/tail/id/${item.value}`" />
</template>
</v-list-item>
</template>
</v-autocomplete>
</template>
@@ -11,12 +11,9 @@
<style scoped> <style scoped>
.glass-box { .glass-box {
width: 50%; width: 50%;
height: 50dvh; min-height: 50dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap:1em; gap:1em;
padding: 2em; padding: 2em;
} }
</style> </style>
+83 -9
View File
@@ -1,24 +1,98 @@
<script setup lang="ts"> <script setup lang="ts">
import MainLayout from "@/Layouts/MainLayout.vue"; import MainLayout from "@/Layouts/MainLayout.vue";
import GlassBox from "@/Components/FlightsGoneBy/GlassBox.vue"; import GlassBox from "@/Components/FlightsGoneBy/GlassBox.vue";
import {Head} from "@inertiajs/vue3"; import { Head } from "@inertiajs/vue3";
import { ref } from "vue";
import {VFileInput} from "vuetify/components";
import {getCsrfToken} from "@/utils/helpers";
defineOptions({
layout: MainLayout defineOptions({ layout: MainLayout });
})
const status = ref<"idle" | "uploading" | "success" | "error">("idle");
const importedCount = ref(0);
const errors = ref<string[]>([]);
const fileInput = ref<InstanceType<typeof VFileInput> | null>(null);
const selectedFile = ref<File | null>(null);
async function onFileChange(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
const actualFile = Array.isArray(file) ? file[0] : file;
if (!actualFile) return;
if (!file) return;
status.value = "uploading";
errors.value = [];
const form = new FormData();
form.append("csv", file);
try {
const response = await fetch(route("flights.import.store"), {
method: "POST",
headers: {
"X-CSRF-TOKEN": getCsrfToken(),
Accept: "application/json",
},
body: form,
});
const data = await response.json();
if (!response.ok) {
errors.value = data.errors ?? [data.message ?? "Import failed."];
status.value = "error";
} else {
importedCount.value = data.imported;
status.value = "success";
}
} catch (e) {
console.error("Fetch error:", e);
errors.value = [String(e)];
status.value = "error";
}
}
</script> </script>
<template> <template>
<Head title="Import" /> <Head title="Import" />
<GlassBox> <GlassBox style="display: flex;flex-direction: column;align-items: center;justify-content: center;">
<h2>Import Your Flights</h2> <h2>Import Your Flights</h2>
<p> <p v-if="status !== 'success'">
Import a CSV export from MyFlightRadar24. You will then be guided to reconcile any data mismatches. Import a CSV export from MyFlightRadar24. You will then be guided
to reconcile any data mismatches.
</p> </p>
<v-file-input style="width:100%; flex:0" label="Select CSV File" accept=".csv" /> <p v-else-if="status === 'success'" type="success" >
Successfully imported {{ importedCount }} flight{{ importedCount !== 1 ? 's' : '' }}. You will just need to reconcile some mismatched airlines and airports.
</p>
<div style="flex:0;width: 100%;display:flex;flex-direction:column;align-items: center;justify-content: center;gap:2em;">
<v-file-input
style="flex:0;width:100%"
prepend-icon=""
v-if="status !== 'uploading' && status !== 'success'"
label="Select CSV File"
accept=".csv"
@change="onFileChange"
ref="fileInput"
v-model="selectedFile"
/>
<div v-else-if="status === 'uploading'" class="d-flex align-center ga-3 mt-2">
<v-progress-circular indeterminate color="primary" />
<span>Importing your flights</span>
</div>
<v-alert closable v-if="status === 'error'" type="error">
<div v-for="(err, i) in errors" :key="i">{{ err }}</div>
</v-alert>
<v-btn :href="route('reconcile')" v-if="status === 'success'" variant="tonal" size="x-large" block >Reconcile Your Data</v-btn>
</div>
</GlassBox> </GlassBox>
</template> </template>
<style scoped> <style scoped>
h2{ h2{
font-size: 2rem; font-size: 2rem;
+165
View File
@@ -0,0 +1,165 @@
<script setup lang="ts">
import MainLayout from "@/Layouts/MainLayout.vue";
import GlassBox from "@/Components/FlightsGoneBy/GlassBox.vue";
import {Head, useForm} from "@inertiajs/vue3";
import AirlineSearchBox from "@/Components/FlightsGoneBy/AirlineSearchBox.vue";
defineOptions({ layout: MainLayout });
const props = defineProps<{
flight: {
flight_reasons: { value: number, title: string }[]
flight_classes:{ value: number, title: string }[]
seat_types: { value: number, title: string }[]
seat_type: number,
'flight_class': number,
'flight_reason': number,
flight_number: string
date: string
registration: string
seat_number: string
note: string
dep_time: string
arr_time: string
duration: string
airline_options: { value: number, title: string }[]
}
}>()
const flight = props.flight;
const form = useForm({
date: flight.date,
flight_number: flight.flight_number,
from: '',
to: '',
dep_time: flight.dep_time,
arr_time: flight.arr_time,
duration: flight.duration,
airline_id: flight.airline_options[0] ?? null,
aircraft: '',
registration: flight.registration,
seat_number: flight.seat_number,
seat_type: flight.seat_types[flight.seat_type],
flight_class: flight.flight_classes[flight.flight_class],
flight_reason: flight.flight_reasons[flight.flight_reason],
note: flight.note,
});
function submit() {
form.post(route('reconcile.store'));
}
</script>
<template>
<Head title="Reconcile"></Head>
<GlassBox>
<h2>Reconcile {{flight.flight_number.length > 1 ? 'Flight ' + flight.flight_number: 'This Flight'}}</h2>
<p>Review and correct your imported flight details before they're saved.</p>
<v-form style="width: 100%">
<v-container>
<!-- Date / Flight Number / Registration -->
<v-row>
<v-col cols="12" md="4">
<v-text-field type="date" v-model="form.date" label="Date" placeholder="03-18-11" :error-messages="form.errors.date" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="form.flight_number" label="Flight Number" placeholder="UL227" :error-messages="form.errors.flight_number" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="form.registration" label="Registration" placeholder="4R-ALX" :error-messages="form.errors.registration" />
</v-col>
</v-row>
<!-- From / To -->
<v-row>
<v-col cols="12" md="6">
<v-autocomplete label="From" :items="[]" placeholder="Dubai International (DXB)" :error-messages="form.errors.from" hide-no-data clearable />
</v-col>
<v-col cols="12" md="6">
<v-autocomplete label="To" :items="[]" placeholder="Kuwait (KWI)" :error-messages="form.errors.to" hide-no-data clearable />
</v-col>
</v-row>
<!-- Dep time / Arr time / Duration -->
<v-row>
<v-col cols="12" md="4">
<v-text-field v-model="form.dep_time" type="time" label="Departure Time" placeholder="09:30" :error-messages="form.errors.dep_time" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="form.arr_time" type="time" label="Arrival Time" placeholder="11:45" :error-messages="form.errors.arr_time" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="form.duration" label="Duration" placeholder="1:23" :error-messages="form.errors.duration" />
</v-col>
</v-row>
<!-- Airline / Aircraft -->
<v-row>
<v-col cols="12" md="6">
<AirlineSearchBox
v-model="form.airline_id"
:prefilled-options="flight.airline_options"
:error-messages="form.errors.airline_id"
/>
</v-col>
<v-col cols="12" md="6">
<v-autocomplete label="Aircraft" :items="[]" placeholder="Airbus A330" :error-messages="form.errors.aircraft" hide-no-data clearable />
</v-col>
</v-row>
<!-- Seat Number / Seat Type / Flight Class / Flight Reason -->
<v-row>
<v-col cols="12" md="3">
<v-text-field v-model="form.seat_number" label="Seat Number" placeholder="22A" :error-messages="form.errors.seat_number" />
</v-col>
<v-col cols="12" md="3">
<v-select v-model="form.seat_type" label="Seat Type" :items="flight.seat_types" :error-messages="form.errors.seat_type" clearable />
</v-col>
<v-col cols="12" md="3">
<v-select v-model="form.flight_class" label="Class" :items="flight.flight_classes" :error-messages="form.errors.flight_class" clearable />
</v-col>
<v-col cols="12" md="3">
<v-select v-model="form.flight_reason" label="Reason" :items="flight.flight_reasons" :error-messages="form.errors.flight_reason" clearable />
</v-col>
</v-row>
<!-- Note -->
<v-row>
<v-col cols="12">
<v-textarea v-model="form.note" label="Note" placeholder="Any additional notes…" :error-messages="form.errors.note" rows="3" auto-grow />
</v-col>
</v-row>
<!-- Submit -->
<v-row>
<v-col cols="12" class="d-flex justify-end">
<v-btn size="large" :loading="form.processing" :disabled="form.processing" @click="submit">
Save Flight
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</GlassBox>
</template>
<style scoped>
h2 {
font-size: 2rem;
text-align: center;
}
p {
text-align: center;
width: 100%;
opacity: 0.7;
margin-bottom: 1rem;
}
</style>
+1
View File
@@ -6,6 +6,7 @@ import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy'; import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import { createApp, h, DefineComponent } from 'vue'; import { createApp, h, DefineComponent } from 'vue';
import vuetify from './plugins/vuetify'; import vuetify from './plugins/vuetify';
import '@mdi/font/css/materialdesignicons.css'
const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
+4
View File
@@ -1,8 +1,12 @@
import 'vuetify/styles' import 'vuetify/styles'
import { createVuetify } from 'vuetify' import { createVuetify } from 'vuetify'
export default createVuetify({ export default createVuetify({
theme: { theme: {
defaultTheme: 'dark', defaultTheme: 'dark',
}, },
icons: {
defaultSet: 'mdi',
},
}) })
+3
View File
@@ -0,0 +1,3 @@
export function getCsrfToken(): string {
return (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '';
}
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title inertia>{{ config('app.name', 'Laravel') }}</title> <title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts --> <!-- Fonts -->
+43 -3
View File
@@ -1,7 +1,9 @@
<?php <?php
use App\Http\Controllers\FlightImportController;
use App\Http\Controllers\LogoController; use App\Http\Controllers\LogoController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use App\Models\Airline;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Inertia\Inertia; use Inertia\Inertia;
@@ -15,15 +17,52 @@ Route::domain(config('app.domain'))->group(
return Inertia::render('Index'); return Inertia::render('Index');
}); });
Route::get('/import/fr24', function () {
return Inertia::render('Fr24Import');
});
Route::get('/dashboard', function () { Route::get('/dashboard', function () {
return Inertia::render('Dashboard'); return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard'); })->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
Route::get('/import/fr24', function () {
if (Auth::user()->importedFlights()->exists()) {
return redirect()->route('reconcile');
}
return Inertia::render('Fr24Import');
})->name('import.fr24');
Route::get('/reconcile', function () {
$flight = new FlightImportController()->reconcile(request());
if (!$flight) {
return redirect('/');
}
return Inertia::render('ReconcileFlight', [
'flight' => $flight,
]);
})->name('reconcile');
Route::get('/airlines/search', function () {
$q = request('q', '');
return Airline::orderByDesc('active')
->where(function ($query) use ($q) {
$query->where('name', 'ilike', "%{$q}%")
->orWhere('IATA_code', 'ilike', "%{$q}%")
->orWhere('ICAO_code', 'ilike', "%{$q}%");
})
->limit(15)
->get(['id', 'name', 'IATA_code', 'ICAO_code', 'logo'])
->map(fn($a) => [
'value' => $a->id,
'title' => "{$a->name} ({$a->IATA_code}/{$a->ICAO_code})",
])
->values();
})->name('airlines.search');
Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
@@ -44,4 +83,5 @@ Route::domain(config('app.api_domain'))->group(function () {
}); });
Route::get('airlines/logos/tail/{code}', [LogoController::class, 'getLogoByCode']) Route::get('airlines/logos/tail/{code}', [LogoController::class, 'getLogoByCode'])
->where('code', '[A-Za-z0-9]{2,3}'); ->where('code', '[A-Za-z0-9]{2,3}');
Route::get('airlines/logos/tail/id/{id}', [LogoController::class, 'getLogoById']);
}); });