Added API

This commit is contained in:
2026-06-21 12:52:30 +10:00
parent 05ca994253
commit 5850c849d0
32 changed files with 248 additions and 107 deletions
+24 -3
View File
@@ -3,22 +3,27 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\ApiController;
use App\Http\Controllers\UserFlightController;
use App\Models\User;
use App\Models\UserFlight;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
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);
}
if (Gate::denies('viewProfileData', $user)) {
return response()->json(['message' => 'Cannot access private user.'], 403);
}
$flight = UserFlight::with(['departureAirport', 'arrivalAirport', 'airline', 'aircraft'])
->where('user_id', $user->id)
->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
{
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){
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 {
@@ -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.');
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Sanctum\HasApiTokens;
class Guest extends Model
{
use HasApiTokens;
}
+37
View File
@@ -12,6 +12,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Traits\HasAchievements;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
@@ -96,6 +98,41 @@ class User extends Authenticatable
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
{
return $this->hasMany(ImportedFlight::class);
+9
View File
@@ -8,6 +8,9 @@ use App\Observers\AirlineObserver;
use App\Observers\FlightObserver;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiting\Limit;
class AppServiceProvider extends ServiceProvider
{
@@ -27,5 +30,11 @@ class AppServiceProvider extends ServiceProvider
Vite::prefetch(concurrency: 3);
UserFlight::observe(FlightObserver::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());
});
}
}