Added API
This commit is contained in:
@@ -7,6 +7,8 @@ APP_URL=http://localhost
|
|||||||
APP_DOMAIN=flightsgoneby.test
|
APP_DOMAIN=flightsgoneby.test
|
||||||
API_DOMAIN=api.flightsgoneby.test
|
API_DOMAIN=api.flightsgoneby.test
|
||||||
|
|
||||||
|
TRUSTED_FRONTEND_ORIGINS=https://app.example.com
|
||||||
|
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
APP_FALLBACK_LOCALE=en
|
APP_FALLBACK_LOCALE=en
|
||||||
APP_FAKER_LOCALE=en_US
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|||||||
@@ -3,22 +3,27 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Http\Controllers\ApiController;
|
use App\Http\Controllers\ApiController;
|
||||||
|
use App\Http\Controllers\UserFlightController;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserFlight;
|
use App\Models\UserFlight;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class UserApiController extends ApiController
|
class UserApiController extends ApiController
|
||||||
{
|
{
|
||||||
public function nextFlight(string $username): JsonResponse
|
public function nextFlight(User $user): JsonResponse
|
||||||
{
|
{
|
||||||
$user = User::where('name', 'ilike', $username)->first();
|
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user->id) {
|
||||||
return response()->json(['message' => 'User not found'], 404);
|
return response()->json(['message' => 'User not found'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Gate::denies('viewProfileData', $user)) {
|
||||||
|
return response()->json(['message' => 'Cannot access private user.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
$flight = UserFlight::with(['departureAirport', 'arrivalAirport', 'airline', 'aircraft'])
|
$flight = UserFlight::with(['departureAirport', 'arrivalAirport', 'airline', 'aircraft'])
|
||||||
->where('user_id', $user->id)
|
->where('user_id', $user->id)
|
||||||
->where('departure_date', '>', now()->utc())
|
->where('departure_date', '>', now()->utc())
|
||||||
@@ -50,4 +55,20 @@ class UserApiController extends ApiController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewableFlights(User $user)
|
||||||
|
{
|
||||||
|
if (Gate::denies('viewProfileData', $user)) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
return $user->flightsWithRelationshipsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewableDepartedFlights(User $user)
|
||||||
|
{
|
||||||
|
if (Gate::denies('viewProfileData', $user)) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
return $user->flightsWithRelationshipsLoaded('departed');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,48 +11,5 @@ use Illuminate\Support\Facades\Gate;
|
|||||||
class UserFlightController extends Controller
|
class UserFlightController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
public function viewableFlights(User $user, ?Request $request = null)
|
|
||||||
{
|
|
||||||
if (Gate::denies('viewProfileData', $user)) {
|
|
||||||
return response()->json([]);
|
|
||||||
}
|
|
||||||
return $this->flights($user, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class UserProfileController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function getUserFlightApiURL(User $user){
|
public static function getUserFlightApiURL(User $user){
|
||||||
return '/data/user/'.$user->name.'/flights';
|
return config('app.logo_api_url').'/user/'.$user->name.'/flights';
|
||||||
|
//return '/data/user/'.$user->name.'/flights';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function profileData(User $user, string $view, ?int $selectedFlightId = null) : array {
|
public function profileData(User $user, string $view, ?int $selectedFlightId = null) : array {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class SanctumOrTrustedOrigin
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
// Authenticated via Sanctum (cookie or token) — let it through, auth()->user() is set.
|
||||||
|
if ($request->user('sanctum')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthenticated, but coming from our own frontend — let it through too.
|
||||||
|
$origin = $request->headers->get('Origin') ?? $request->headers->get('Referer');
|
||||||
|
$trusted = config('app.trusted_frontend_origins', []);
|
||||||
|
|
||||||
|
foreach ($trusted as $trustedOrigin) {
|
||||||
|
if ($origin && str_starts_with($origin, $trustedOrigin)) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abort(403, 'Forbidden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class Guest extends Model
|
||||||
|
{
|
||||||
|
use HasApiTokens;
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use App\Traits\HasAchievements;
|
use App\Traits\HasAchievements;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
|
||||||
@@ -96,6 +98,41 @@ class User extends Authenticatable
|
|||||||
return $this->flights()->where('departure_date', '>=', now('UTC'));
|
return $this->flights()->where('departure_date', '>=', now('UTC'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function flightsWithRelationshipsLoaded(?string $filter = null): Collection
|
||||||
|
{
|
||||||
|
$key = "user_flights_{$this->id}";
|
||||||
|
|
||||||
|
$json = Cache::remember($key, now()->addDays(30), function () {
|
||||||
|
return $this->flights()
|
||||||
|
->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();
|
||||||
|
});
|
||||||
|
|
||||||
|
$collection = collect(json_decode($json));
|
||||||
|
$today = now('UTC')->toDateString();
|
||||||
|
|
||||||
|
return match ($filter) {
|
||||||
|
'departed' => $collection->filter(fn($f) => $f->departure_date <= $today)->values(),
|
||||||
|
'upcoming' => $collection->filter(fn($f) => $f->departure_date > $today)->values(),
|
||||||
|
default => $collection,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public function ImportedFlights(): HasMany
|
public function ImportedFlights(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(ImportedFlight::class);
|
return $this->hasMany(ImportedFlight::class);
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use App\Observers\AirlineObserver;
|
|||||||
use App\Observers\FlightObserver;
|
use App\Observers\FlightObserver;
|
||||||
use Illuminate\Support\Facades\Vite;
|
use Illuminate\Support\Facades\Vite;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -27,5 +30,11 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
Vite::prefetch(concurrency: 3);
|
Vite::prefetch(concurrency: 3);
|
||||||
UserFlight::observe(FlightObserver::class);
|
UserFlight::observe(FlightObserver::class);
|
||||||
Airline::observe(AirlineObserver::class);
|
Airline::observe(AirlineObserver::class);
|
||||||
|
RateLimiter::for('api', function (Request $request) {
|
||||||
|
return $request->user()
|
||||||
|
? Limit::perMinute(60)->by($request->user()->id)
|
||||||
|
: Limit::perMinute(10)->by($request->ip());
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-3
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Middleware\HandleInertiaRequests;
|
use App\Http\Middleware\HandleInertiaRequests;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
@@ -11,6 +12,7 @@ use Illuminate\Http\Response;
|
|||||||
use Spatie\Permission\Middleware\PermissionMiddleware;
|
use Spatie\Permission\Middleware\PermissionMiddleware;
|
||||||
use Spatie\Permission\Middleware\RoleMiddleware;
|
use Spatie\Permission\Middleware\RoleMiddleware;
|
||||||
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
|
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
@@ -19,6 +21,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
api: __DIR__.'/../routes/api.php',
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
|
apiPrefix: '',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->web(append: [
|
$middleware->web(append: [
|
||||||
@@ -33,9 +36,26 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
//
|
//
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
|
||||||
|
if ($request->getHost() !== config('app.api_domain')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e->getPrevious() instanceof ModelNotFoundException) {
|
||||||
|
return response()->json(['message' => 'Resource not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Not found.'], 404);
|
||||||
|
});
|
||||||
|
|
||||||
$exceptions->respond(function ($response, Throwable $e, Request $request) {
|
$exceptions->respond(function ($response, Throwable $e, Request $request) {
|
||||||
$status = $response->getStatusCode();
|
$status = $response->getStatusCode();
|
||||||
|
|
||||||
|
// API domain: never touch the response, let Laravel's own JSON rendering stand.
|
||||||
|
if ($request->getHost() === config('app.api_domain')) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
$errors = [
|
$errors = [
|
||||||
403 => [
|
403 => [
|
||||||
'title' => "The Cockpit is Off Limits",
|
'title' => "The Cockpit is Off Limits",
|
||||||
@@ -64,10 +84,8 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
];
|
];
|
||||||
|
|
||||||
$isLocal = app()->environment(['local', 'testing']);
|
$isLocal = app()->environment(['local', 'testing']);
|
||||||
$handled = array_keys($errors);
|
|
||||||
$friendlyErrorsOnLocal = [404, 403];
|
$friendlyErrorsOnLocal = [404, 403];
|
||||||
|
|
||||||
// In local/testing, only handle 404. In production, handle all.
|
|
||||||
$shouldHandle = isset($errors[$status]) && (
|
$shouldHandle = isset($errors[$status]) && (
|
||||||
!$isLocal || in_array($status, $friendlyErrorsOnLocal)
|
!$isLocal || in_array($status, $friendlyErrorsOnLocal)
|
||||||
);
|
);
|
||||||
@@ -88,5 +106,13 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
])
|
])
|
||||||
->toResponse($request)
|
->toResponse($request)
|
||||||
->setStatusCode($status);
|
->setStatusCode($status);
|
||||||
|
})
|
||||||
|
->shouldRenderJsonWhen(function ($request, Throwable $e) {
|
||||||
|
if ($request->getHost() === config('app.api_domain')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->expectsJson();
|
||||||
});
|
});
|
||||||
})->create();
|
})
|
||||||
|
->create();
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ return [
|
|||||||
'api_domain' => env('API_DOMAIN', 'api.flightsgoneby.com'),
|
'api_domain' => env('API_DOMAIN', 'api.flightsgoneby.com'),
|
||||||
'logo_api_url' => env('LOGO_API_URL', 'https://api.flightsgoneby.com'),
|
'logo_api_url' => env('LOGO_API_URL', 'https://api.flightsgoneby.com'),
|
||||||
'timezone_api_key' => env('TIMEZONE_API_KEY', '1234567890'),
|
'timezone_api_key' => env('TIMEZONE_API_KEY', '1234567890'),
|
||||||
|
'trusted_frontend_origins' => array_filter(explode(',', env('TRUSTED_FRONTEND_ORIGINS', ''))),
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Timezone
|
| Application Timezone
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure your settings for cross-origin resource sharing
|
||||||
|
| or "CORS". This determines what cross-origin operations may execute
|
||||||
|
| in web browsers. You are free to adjust these settings as needed.
|
||||||
|
|
|
||||||
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'paths' => ['*'],
|
||||||
|
|
||||||
|
'allowed_methods' => ['*'],
|
||||||
|
|
||||||
|
'allowed_origins' => [
|
||||||
|
'https://flightsgoneby.com',
|
||||||
|
'https://www.flightsgoneby.com',
|
||||||
|
'http://flightsgoneby.test:8000',
|
||||||
|
],
|
||||||
|
|
||||||
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
|
'allowed_headers' => ['*'],
|
||||||
|
|
||||||
|
'exposed_headers' => [],
|
||||||
|
|
||||||
|
'max_age' => 0,
|
||||||
|
|
||||||
|
'supports_credentials' => true,
|
||||||
|
|
||||||
|
];
|
||||||
@@ -19,7 +19,7 @@ interface AirlineEntry {
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
alliance: Alliance
|
alliance: Alliance
|
||||||
airlines: Airline[]
|
airlines: Airline[]
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ export function useFlights(url: string, departedOnly: boolean = false) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url, {
|
const requestUrl = departedOnly ? `${url}/departed` : url
|
||||||
params: departedOnly ? { departed_only: true } : {}
|
const response = await axios.get(requestUrl)
|
||||||
})
|
|
||||||
flights.value = response.data
|
flights.value = response.data
|
||||||
} finally {
|
} finally {
|
||||||
flightsLoading.value = false
|
flightsLoading.value = false
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
families: Record<string, string[]>
|
families: Record<string, string[]>
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
families: Record<string, string[]>
|
families: Record<string, string[]>
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
alliance: Alliance
|
alliance: Alliance
|
||||||
airlines: Airline[]
|
airlines: Airline[]
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
@@ -16,7 +16,7 @@ defineProps<{
|
|||||||
<AllianceChallenge
|
<AllianceChallenge
|
||||||
:achievement="achievement"
|
:achievement="achievement"
|
||||||
:user="user"
|
:user="user"
|
||||||
:isFollowing="isFollowing"
|
:followStatus="followStatus"
|
||||||
:alliance="alliance"
|
:alliance="alliance"
|
||||||
:airlines="airlines"
|
:airlines="airlines"
|
||||||
:flights="flights"
|
:flights="flights"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
alliance: Alliance
|
alliance: Alliance
|
||||||
airlines: Airline[]
|
airlines: Airline[]
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
alliance: Alliance
|
alliance: Alliance
|
||||||
airlines: Airline[]
|
airlines: Airline[]
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
@@ -16,7 +16,7 @@ defineProps<{
|
|||||||
<AllianceChallenge
|
<AllianceChallenge
|
||||||
:achievement="achievement"
|
:achievement="achievement"
|
||||||
:user="user"
|
:user="user"
|
||||||
:isFollowing="isFollowing"
|
:followStatus="followStatus"
|
||||||
:alliance="alliance"
|
:alliance="alliance"
|
||||||
:airlines="airlines"
|
:airlines="airlines"
|
||||||
:flights="flights"
|
:flights="flights"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
alliance: Alliance
|
alliance: Alliance
|
||||||
airlines: Airline[]
|
airlines: Airline[]
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
@@ -16,7 +16,7 @@ defineProps<{
|
|||||||
<AllianceChallenge
|
<AllianceChallenge
|
||||||
:achievement="achievement"
|
:achievement="achievement"
|
||||||
:user="user"
|
:user="user"
|
||||||
:isFollowing="isFollowing"
|
:followStatus="followStatus"
|
||||||
:alliance="alliance"
|
:alliance="alliance"
|
||||||
:airlines="airlines"
|
:airlines="airlines"
|
||||||
:flights="flights"
|
:flights="flights"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
continents: Continent[]
|
continents: Continent[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
continents: Continent[]
|
continents: Continent[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defineOptions({ inheritAttrs: false })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
achievement: Achievement
|
achievement: Achievement
|
||||||
user: User
|
user: User
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flights: Flight[]
|
flights: Flight[]
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const props = defineProps<{
|
|||||||
userAchievement: UserAchievement | null
|
userAchievement: UserAchievement | null
|
||||||
user: User
|
user: User
|
||||||
loggedInUser: User | null
|
loggedInUser: User | null
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
flight_api_url: string
|
flight_api_url: string
|
||||||
regions: Region[]
|
regions: Region[]
|
||||||
alliance: string | null
|
alliance: string | null
|
||||||
@@ -57,7 +57,7 @@ const unlocked = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ProfileLayout :title="`${achievement.name}`" :canView="canView" :achievementCount="achievementCount" :user="user" :isFollowing="isFollowing" :loading="flightsLoading">
|
<ProfileLayout :title="`${achievement.name}`" :canView="canView" :achievementCount="achievementCount" :user="user" :followStatus="followStatus" :loading="flightsLoading">
|
||||||
<div class="innerLayout">
|
<div class="innerLayout">
|
||||||
<ButtonLink variant="flat" icon="mdi-arrow-left" :label="`Back to ${user.name}'s Achievements`" :href="`${route('profile.achievements', { user: user.name })}#${achievement.internal_name}`" />
|
<ButtonLink variant="flat" icon="mdi-arrow-left" :label="`Back to ${user.name}'s Achievements`" :href="`${route('profile.achievements', { user: user.name })}#${achievement.internal_name}`" />
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ const unlocked = computed(() => {
|
|||||||
:flights="flights"
|
:flights="flights"
|
||||||
:achievement="achievement"
|
:achievement="achievement"
|
||||||
:user="user"
|
:user="user"
|
||||||
:isFollowing="isFollowing"
|
:followStatus="followStatus"
|
||||||
:airlines="airlines"
|
:airlines="airlines"
|
||||||
:alliance="alliance"
|
:alliance="alliance"
|
||||||
:continents="continents"
|
:continents="continents"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const props = defineProps<{
|
|||||||
user: User
|
user: User
|
||||||
flight: Flight
|
flight: Flight
|
||||||
flightCount: number
|
flightCount: number
|
||||||
isFollowing: boolean
|
followStatus: string
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
canView: boolean
|
canView: boolean
|
||||||
}>()
|
}>()
|
||||||
@@ -28,7 +28,7 @@ const props = defineProps<{
|
|||||||
<ProfileLayout
|
<ProfileLayout
|
||||||
:canView="canView"
|
:canView="canView"
|
||||||
:user="user"
|
:user="user"
|
||||||
:is-following="isFollowing"
|
:followStatus="followStatus"
|
||||||
:flight-count="flightCount"
|
:flight-count="flightCount"
|
||||||
:loading="false">
|
:loading="false">
|
||||||
<Head :title="`${flight.flight_number ?? user.name + '\'s Flight'}`" />
|
<Head :title="`${flight.flight_number ?? user.name + '\'s Flight'}`" />
|
||||||
|
|||||||
+38
-8
@@ -1,15 +1,45 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\SettingsController;
|
use App\Http\Controllers\Api\AircraftApiController;
|
||||||
use Illuminate\Http\Request;
|
use App\Http\Controllers\Api\AirlineApiController;
|
||||||
|
use App\Http\Controllers\Api\UserApiController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/user', function (Request $request) {
|
Route::domain(config('app.api_domain'))->group(function () {
|
||||||
return $request->user();
|
|
||||||
})->middleware('auth:sanctum');
|
/* Public Routes */
|
||||||
|
Route::get('/', function () {
|
||||||
|
return response()->json(['message' => 'Welcome to the FlightsGoneBy API']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('user')->controller(UserApiController::class)->group(function () {
|
||||||
|
Route::get('{user}/flights', 'viewableFlights')->name('api.user.flights');
|
||||||
|
Route::get('{user}/flights/departed', 'viewableDepartedFlights')->name('api.user.flights');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* Image Routes */
|
||||||
|
Route::prefix('airline')->controller(AirlineApiController::class)->group(function () {
|
||||||
|
Route::get('{internalName}/logo/tail', 'getLogoByInternalName')->name('airline.logo.tail');
|
||||||
|
Route::get('{airlineInternalName}/livery/{aircraftDesignator}', 'getLivery')->name('airline.livery');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('aircraft')->controller(AircraftApiController::class)->group(function () {
|
||||||
|
Route::get('{aircraftDesignator}/livery', 'getLivery')->name('aircraft.livery');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*Authenticated Routes*/
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::prefix('airline')->controller(AirlineApiController::class)->group(function () {
|
||||||
|
Route::get('{internalName}', 'get')->name('airline.show');
|
||||||
|
Route::get('code/{code}', 'getByCode')->name('airline.code.index');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::prefix('user')->controller(UserApiController::class)->group(function () {
|
||||||
|
Route::get('{user}/flights/next', 'nextFlight')->name('api.user.flights.next');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Route::middleware('auth:sanctum')->group(function () {
|
|
||||||
Route::get('/settings', [SettingsController::class, 'show']);
|
|
||||||
Route::patch('/settings', [SettingsController::class, 'update']);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ Route::domain(config('app.domain'))->group(
|
|||||||
Route::get('/search/aircraft', [SearchController::class, 'aircraft'])->name('search.aircraft');
|
Route::get('/search/aircraft', [SearchController::class, 'aircraft'])->name('search.aircraft');
|
||||||
Route::get('/search/airports', [SearchController::class, 'airports'])->name('search.airports');
|
Route::get('/search/airports', [SearchController::class, 'airports'])->name('search.airports');
|
||||||
|
|
||||||
Route::get('/data/user/{user}/flights', [UserFlightController::class, 'viewableFlights']);
|
|
||||||
Route::get('/u/{user}', [UserProfileController::class, 'view'])->name('profile.view');
|
Route::get('/u/{user}', [UserProfileController::class, 'view'])->name('profile.view');
|
||||||
Route::get('/u/{user}/map', [UserProfileController::class, 'map'])->name('profile.map');
|
Route::get('/u/{user}/map', [UserProfileController::class, 'map'])->name('profile.map');
|
||||||
Route::get('/u/{user}/departure-board/{flight?}', [UserProfileController::class, 'departureBoard'])->name('profile.departure-board');
|
Route::get('/u/{user}/departure-board/{flight?}', [UserProfileController::class, 'departureBoard'])->name('profile.departure-board');
|
||||||
@@ -108,25 +107,5 @@ Route::domain(config('app.domain'))->group(
|
|||||||
* API Routes
|
* API Routes
|
||||||
*/
|
*/
|
||||||
Route::domain(config('app.api_domain'))->group(function () {
|
Route::domain(config('app.api_domain'))->group(function () {
|
||||||
Route::get('/', function () {
|
|
||||||
return response()->json(['message' => 'Welcome to the FlightsGoneBy API']);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::prefix('airline')->controller(AirlineApiController::class)->group(function () {
|
|
||||||
Route::get('{internalName}', 'get')->name('airline.show');
|
|
||||||
Route::get('code/{code}', 'getByCode')->name('airline.code.index');
|
|
||||||
Route::get('{internalName}/logo/tail', 'getLogoByInternalName')->name('airline.logo.tail');
|
|
||||||
Route::get('{airlineInternalName}/livery/{aircraftDesignator}', 'getLivery')->name('airline.livery');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::prefix('aircraft')->controller(AircraftApiController::class)->group(function () {
|
|
||||||
Route::get('{aircraftDesignator}/livery', 'getLivery')->name('aircraft.livery');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Route::prefix('user')->controller(UserApiController::class)->group(function () {
|
|
||||||
Route::get('{username}/next-flight', 'nextFlight');
|
|
||||||
Route::get('{username}/flights', 'flights');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user