Session api token validation
This commit is contained in:
@@ -3,7 +3,9 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\UserAction;
|
use App\Models\UserAction;
|
||||||
|
use App\Models\UserFlight;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class FeedController extends Controller
|
class FeedController extends Controller
|
||||||
@@ -28,4 +30,49 @@ class FeedController extends Controller
|
|||||||
'feed' => $feed,
|
'feed' => $feed,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function following()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
return Cache::remember(
|
||||||
|
"user:{$user->id}:following",
|
||||||
|
now()->addMinutes(15),
|
||||||
|
fn () => $user->following()
|
||||||
|
->with('followee')
|
||||||
|
->get()
|
||||||
|
->pluck('followee')
|
||||||
|
->filter()
|
||||||
|
->values()
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function followingFlights()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
return Cache::remember(
|
||||||
|
"user_following_flights_{$user->id}",
|
||||||
|
now()->addDays(30),
|
||||||
|
function () use ($user) {
|
||||||
|
$followingIds = $user->following()
|
||||||
|
->with('followee')
|
||||||
|
->get()
|
||||||
|
->pluck('followee')
|
||||||
|
->filter()
|
||||||
|
->reject(fn ($followee) => ($followee->settings['profile_privacy'] ?? 'public') === 'private')
|
||||||
|
->pluck('id');
|
||||||
|
|
||||||
|
return UserFlight::query()
|
||||||
|
->with(['departureAirport', 'arrivalAirport', 'user'])
|
||||||
|
->whereIn('user_id', $followingIds)
|
||||||
|
->orderByDesc('departure_date')
|
||||||
|
->limit(100)
|
||||||
|
->get()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use App\Models\Notification;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class FollowerController extends Controller
|
class FollowerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -24,6 +26,43 @@ class FollowerController extends Controller
|
|||||||
return response()->json($followers);
|
return response()->json($followers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
$this->clearFollowingCache(auth()->user());
|
||||||
|
return response()->json(['status' => 'none']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$canView = Gate::allows('viewProfileData', $user);
|
||||||
|
|
||||||
|
Followee::create([
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'followee_id' => $user->id,
|
||||||
|
'verified' => $canView,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->clearFollowingCache(auth()->user());
|
||||||
|
|
||||||
|
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 approve(User $follower): JsonResponse
|
public function approve(User $follower): JsonResponse
|
||||||
{
|
{
|
||||||
$followee = Followee::where('user_id', $follower->id)
|
$followee = Followee::where('user_id', $follower->id)
|
||||||
@@ -33,6 +72,8 @@ class FollowerController extends Controller
|
|||||||
|
|
||||||
$followee->update(['verified' => true]);
|
$followee->update(['verified' => true]);
|
||||||
|
|
||||||
|
$this->clearFollowingCache($follower);
|
||||||
|
|
||||||
Notification::create([
|
Notification::create([
|
||||||
'user_id' => $follower->id,
|
'user_id' => $follower->id,
|
||||||
'title' => 'Follow request accepted',
|
'title' => 'Follow request accepted',
|
||||||
@@ -51,6 +92,8 @@ class FollowerController extends Controller
|
|||||||
->pending()
|
->pending()
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
|
$this->clearFollowingCache($follower);
|
||||||
|
|
||||||
return response()->json(['status' => 'denied']);
|
return response()->json(['status' => 'denied']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +104,14 @@ class FollowerController extends Controller
|
|||||||
->verified()
|
->verified()
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
|
$this->clearFollowingCache($follower);
|
||||||
|
|
||||||
return response()->json(['status' => 'removed']);
|
return response()->json(['status' => 'removed']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function clearFollowingCache(User $user): void
|
||||||
|
{
|
||||||
|
Cache::forget("user:{$user->id}:following");
|
||||||
|
Cache::forget("user_following_flights_{$user->id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\UserFlight;
|
use App\Models\UserFlight;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
|
|
||||||
@@ -14,9 +15,9 @@ class HomePageController extends Controller
|
|||||||
{
|
{
|
||||||
return Cache::remember('splash_flights', now()->addHours(12), function () {
|
return Cache::remember('splash_flights', now()->addHours(12), function () {
|
||||||
return UserFlight::query()
|
return UserFlight::query()
|
||||||
->with(['departureAirport', 'arrivalAirport'])
|
->with(['departureAirport', 'arrivalAirport', 'user'])
|
||||||
->whereHas('user', function ($query) {
|
->whereHas('user', function ($query) {
|
||||||
$query->whereRaw("settings->>'profile_privacy' is distinct from 'private'");
|
$query->whereRaw(DB::raw("settings->>'profile_privacy' is distinct from 'private'"));
|
||||||
})
|
})
|
||||||
->orderByDesc('departure_date')
|
->orderByDesc('departure_date')
|
||||||
->limit(50)
|
->limit(50)
|
||||||
|
|||||||
@@ -9,74 +9,12 @@ use App\Settings\SettingsRegistry;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class UserController extends Controller
|
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(['status' => 'none']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$canView = Gate::allows('viewProfileData', $user);
|
|
||||||
|
|
||||||
Followee::create([
|
|
||||||
'user_id' => auth()->id(),
|
|
||||||
'followee_id' => $user->id,
|
|
||||||
'verified' => $canView,
|
|
||||||
]);
|
|
||||||
|
|
||||||
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){
|
public function settings(?string $category = null){
|
||||||
$allowedTabs = ['general', 'followers'];
|
$allowedTabs = ['general', 'followers'];
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Listeners\IssueApiToken;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class RefreshApiToken
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!$request->user() || !session('api_token_id')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $request->user()
|
||||||
|
->tokens()
|
||||||
|
->find(session('api_token_id'));
|
||||||
|
|
||||||
|
// Refresh if expired or within 30 minutes of expiry
|
||||||
|
if (!$token || $token->expires_at->subMinutes(30)->isPast()) {
|
||||||
|
app(IssueApiToken::class)->createToken($request->user());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,13 +11,26 @@ class IssueApiToken
|
|||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = $event->user;
|
$user = $event->user;
|
||||||
$user->tokens()->where('name', 'frontend')->delete();
|
|
||||||
$token = $user->createToken(
|
$this->createToken($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createToken(User $user): void
|
||||||
|
{
|
||||||
|
// Delete only this session's existing token (if any)
|
||||||
|
if ($tokenId = session('api_token_id')) {
|
||||||
|
$user->tokens()->where('id', $tokenId)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$newToken = $user->createToken(
|
||||||
'frontend',
|
'frontend',
|
||||||
['*'],
|
['*'],
|
||||||
now()->addHours(4)
|
now()->addHours(4)
|
||||||
)->plainTextToken;
|
);
|
||||||
|
|
||||||
session(['api_token' => $token]);
|
session([
|
||||||
|
'api_token' => $newToken->plainTextToken,
|
||||||
|
'api_token_id' => $newToken->accessToken->id,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class RevokeApiToken
|
|||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = $event->user;
|
$user = $event->user;
|
||||||
|
|
||||||
$user?->tokens()->where('name', 'frontend')->delete();
|
if ($tokenId = session('api_token_id')) {
|
||||||
session()->forget('api_token');
|
$user?->tokens()->where('id', $tokenId)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
session()->forget(['api_token', 'api_token_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -140,19 +140,19 @@ class User extends Authenticatable
|
|||||||
|
|
||||||
public function following(): HasMany
|
public function following(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Followee::class, 'user_id');
|
return $this->hasMany(Followee::class, 'user_id')->verified();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function followers(): HasMany
|
public function followers(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Followee::class, 'followee_id');
|
return $this->hasMany(Followee::class, 'followee_id')->verified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isFollowing(User $user): bool
|
public function isFollowing(User $user): bool
|
||||||
{
|
{
|
||||||
return $this->following()
|
return $this->following()
|
||||||
->where('followee_id', $user->id)
|
->where('followee_id', $user->id)
|
||||||
->verified()
|
|
||||||
->exists();
|
->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ class FlightObserver
|
|||||||
protected function clearCache(UserFlight $flight): void
|
protected function clearCache(UserFlight $flight): void
|
||||||
{
|
{
|
||||||
Cache::forget("user_flights_{$flight->user->id}");
|
Cache::forget("user_flights_{$flight->user->id}");
|
||||||
|
|
||||||
|
//Make queued task if the site gets big
|
||||||
|
$flight->user->followers()
|
||||||
|
->get()
|
||||||
|
->each(fn ($follower) => Cache::forget("user_following_flights_{$follower->id}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Middleware\HandleInertiaRequests;
|
use App\Http\Middleware\HandleInertiaRequests;
|
||||||
|
use App\Http\Middleware\RefreshApiToken;
|
||||||
use App\Http\Middleware\SanctumOrTrustedOrigin;
|
use App\Http\Middleware\SanctumOrTrustedOrigin;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
@@ -28,6 +29,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
$middleware->web(append: [
|
$middleware->web(append: [
|
||||||
HandleInertiaRequests::class,
|
HandleInertiaRequests::class,
|
||||||
AddLinkHeadersForPreloadedAssets::class,
|
AddLinkHeadersForPreloadedAssets::class,
|
||||||
|
RefreshApiToken::class
|
||||||
]);
|
]);
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'role' => RoleMiddleware::class,
|
'role' => RoleMiddleware::class,
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {User} from "@/Types/types";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
user: User
|
||||||
|
}>()
|
||||||
|
import {Link} from "@inertiajs/vue3";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="avatar">
|
||||||
|
<Link :href="route('profile.view', { user: user?.name })">
|
||||||
|
{{ user?.name?.charAt(0).toUpperCase() ?? '?' }}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.avatar {
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(99, 102, 241, 0.2);
|
||||||
|
border: 1px solid rgba(99, 102, 241, 0.35);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #818cf8;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,7 @@ import FlightCancelledFeedItem from "@/Components/FlightsGoneBy/Feed/FlightCance
|
|||||||
import FlightImportedFeedItem from "@/Components/FlightsGoneBy/Feed/FlightImportedFeedItem.vue";
|
import FlightImportedFeedItem from "@/Components/FlightsGoneBy/Feed/FlightImportedFeedItem.vue";
|
||||||
import FlightMovedFeedItem from "@/Components/FlightsGoneBy/Feed/FlightMovedFeedItem.vue";
|
import FlightMovedFeedItem from "@/Components/FlightsGoneBy/Feed/FlightMovedFeedItem.vue";
|
||||||
import ButtonLink from "@/Components/FlightsGoneBy/ButtonLink.vue";
|
import ButtonLink from "@/Components/FlightsGoneBy/ButtonLink.vue";
|
||||||
|
import Avatar from "@/Components/FlightsGoneBy/Feed/Avatar.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
action: UserAction
|
action: UserAction
|
||||||
@@ -56,11 +57,7 @@ function timeAgo(dateStr: string): string {
|
|||||||
<template>
|
<template>
|
||||||
<div class="feed-item glass glass-border">
|
<div class="feed-item glass glass-border">
|
||||||
<div class="card-top">
|
<div class="card-top">
|
||||||
<div class="avatar">
|
<Avatar :user="action.user" />
|
||||||
<Link :href="route('profile.view', { user: action.user?.name })">
|
|
||||||
{{ action.user?.name?.charAt(0).toUpperCase() ?? '?' }}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="name">
|
<span class="name">
|
||||||
<Link :href="route('profile.view', { user: action.user?.name })">
|
<Link :href="route('profile.view', { user: action.user?.name })">
|
||||||
@@ -110,21 +107,6 @@ function timeAgo(dateStr: string): string {
|
|||||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 2.25rem;
|
|
||||||
height: 2.25rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(99, 102, 241, 0.2);
|
|
||||||
border: 1px solid rgba(99, 102, 241, 0.35);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #818cf8;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -171,7 +171,11 @@ function airportPopupHTML(airport: Airport): string {
|
|||||||
|
|
||||||
function routePopupHTML(historical: Flight[], future: Flight[]): string {
|
function routePopupHTML(historical: Flight[], future: Flight[]): string {
|
||||||
interface AirlineEntry { html: string; count: number }
|
interface AirlineEntry { html: string; count: number }
|
||||||
interface DirectionGroup { label: string; airlines: Map<string, AirlineEntry> }
|
interface DirectionGroup {
|
||||||
|
label: string
|
||||||
|
airlines: Map<string, AirlineEntry>
|
||||||
|
users: Map<number, { name: string }>
|
||||||
|
}
|
||||||
|
|
||||||
const groupByDirection = (flights: Flight[]): DirectionGroup[] => {
|
const groupByDirection = (flights: Flight[]): DirectionGroup[] => {
|
||||||
const groups = new Map<string, DirectionGroup>()
|
const groups = new Map<string, DirectionGroup>()
|
||||||
@@ -180,13 +184,19 @@ function routePopupHTML(historical: Flight[], future: Flight[]): string {
|
|||||||
const key = `${flight.departure_airport.id}-${flight.arrival_airport.id}`
|
const key = `${flight.departure_airport.id}-${flight.arrival_airport.id}`
|
||||||
const label = `${flight.departure_airport.municipality} to ${flight.arrival_airport.municipality}`
|
const label = `${flight.departure_airport.municipality} to ${flight.arrival_airport.municipality}`
|
||||||
|
|
||||||
if (!groups.has(key)) groups.set(key, { label, airlines: new Map() })
|
if (!groups.has(key)) groups.set(key, { label, airlines: new Map(), users: new Map() })
|
||||||
|
|
||||||
|
const group = groups.get(key)!
|
||||||
|
|
||||||
|
if (flight.user) {
|
||||||
|
group.users.set(flight.user.id, { name: flight.user.name })
|
||||||
|
}
|
||||||
|
|
||||||
if (!flight.airline) return
|
if (!flight.airline) return
|
||||||
|
|
||||||
const { iata_code, logo_url, name } = flight.airline
|
const { iata_code, logo_url, name } = flight.airline
|
||||||
const airlineKey = iata_code ?? ''
|
const airlineKey = iata_code ?? ''
|
||||||
const airlines = groups.get(key)!.airlines
|
const airlines = group.airlines
|
||||||
|
|
||||||
if (airlines.has(airlineKey)) {
|
if (airlines.has(airlineKey)) {
|
||||||
airlines.get(airlineKey)!.count++
|
airlines.get(airlineKey)!.count++
|
||||||
@@ -204,10 +214,20 @@ function routePopupHTML(historical: Flight[], future: Flight[]): string {
|
|||||||
return [...groups.values()]
|
return [...groups.values()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderUsers = (users: Map<number, { name: string }>): string => {
|
||||||
|
if (!users.size) return ''
|
||||||
|
|
||||||
|
const badges = [...users.values()]
|
||||||
|
.map(({ name }) => `<span class="rp-user-badge">${name}</span>`)
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
return `<div class="rp-users">${badges}</div>`
|
||||||
|
}
|
||||||
|
|
||||||
const renderSection = (title: string, flights: Flight[]): string => {
|
const renderSection = (title: string, flights: Flight[]): string => {
|
||||||
if (!flights.length) return ''
|
if (!flights.length) return ''
|
||||||
|
|
||||||
const rows = groupByDirection(flights).map(({ label, airlines }) => {
|
const rows = groupByDirection(flights).map(({ label, airlines, users }) => {
|
||||||
const airlineLines = [...airlines.values()]
|
const airlineLines = [...airlines.values()]
|
||||||
.map(({ html, count }) => count > 1
|
.map(({ html, count }) => count > 1
|
||||||
? `<span style="display:inline-flex;align-items:center;gap:4px;">${html}<span style="color:#556677">(x${count})</span></span>`
|
? `<span style="display:inline-flex;align-items:center;gap:4px;">${html}<span style="color:#556677">(x${count})</span></span>`
|
||||||
@@ -218,6 +238,7 @@ function routePopupHTML(historical: Flight[], future: Flight[]): string {
|
|||||||
<div class="rp-direction">
|
<div class="rp-direction">
|
||||||
<div class="rp-route">${label}</div>
|
<div class="rp-route">${label}</div>
|
||||||
<div class="rp-airlines">${airlineLines || '—'}</div>
|
<div class="rp-airlines">${airlineLines || '—'}</div>
|
||||||
|
${renderUsers(users)}
|
||||||
</div>`
|
</div>`
|
||||||
}).join('')
|
}).join('')
|
||||||
|
|
||||||
@@ -778,13 +799,17 @@ export default defineComponent({
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
|
max-height: 70vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.flight-map-wrapper { aspect-ratio: 7 / 10; }
|
.flight-map-wrapper {
|
||||||
|
aspect-ratio: 7 / 10;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-container { position: absolute; inset: 0; }
|
.map-container { position: absolute; inset: 0; }
|
||||||
@@ -860,6 +885,22 @@ export default defineComponent({
|
|||||||
.rp-airlines { font-size: 0.75rem; color: #778899; }
|
.rp-airlines { font-size: 0.75rem; color: #778899; }
|
||||||
.rp-divider { height: 1px; background: rgba(255,255,255,0.08); margin: 2px 0; }
|
.rp-divider { height: 1px; background: rgba(255,255,255,0.08); margin: 2px 0; }
|
||||||
|
|
||||||
|
.rp-users {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.rp-user-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #c8cdd8;
|
||||||
|
background: rgba(77,166,255,0.12);
|
||||||
|
border: 1px solid rgba(77,166,255,0.25);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Map controls */
|
/* Map controls */
|
||||||
.maplibregl-ctrl-group { background: rgba(10,14,22,0.85) !important; border: 1px solid rgba(255,255,255,0.08) !important; }
|
.maplibregl-ctrl-group { background: rgba(10,14,22,0.85) !important; border: 1px solid rgba(255,255,255,0.08) !important; }
|
||||||
.maplibregl-ctrl-group button { color: #a0b4c8 !important; }
|
.maplibregl-ctrl-group button { color: #a0b4c8 !important; }
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ type FollowStatus = 'following' | 'requested' | 'none'
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: User
|
user: User
|
||||||
followStatus: FollowStatus
|
followStatus: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const snackbar = ref(false)
|
const snackbar = ref(false)
|
||||||
const snackbarMessage = ref('')
|
const snackbarMessage = ref('')
|
||||||
|
|
||||||
const status = ref<FollowStatus>(props.followStatus)
|
const status = ref<FollowStatus>(props.followStatus as FollowStatus)
|
||||||
const processing = ref(false)
|
const processing = ref(false)
|
||||||
|
|
||||||
const auth = usePage<SharedProps>().props.auth
|
const auth = usePage<SharedProps>().props.auth
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const props = defineProps<{
|
|||||||
achievementCount?: number
|
achievementCount?: number
|
||||||
followStatus?: string
|
followStatus?: string
|
||||||
show: "flights" | "achievements"
|
show: "flights" | "achievements"
|
||||||
|
showCount?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const counts = computed(() => {
|
const counts = computed(() => {
|
||||||
@@ -39,7 +40,7 @@ const counts = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="board-count">
|
<div class="board-count" v-if="showCount">
|
||||||
<span class="count-number">{{ counts[show] }}</span>
|
<span class="count-number">{{ counts[show] }}</span>
|
||||||
<span class="count-label">{{show.toUpperCase()}}</span>
|
<span class="count-label">{{show.toUpperCase()}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ defineProps<{
|
|||||||
loading: boolean
|
loading: boolean
|
||||||
canView: boolean
|
canView: boolean
|
||||||
title?: string
|
title?: string
|
||||||
|
showCount?: boolean
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="board-wrapper">
|
<div class="board-wrapper">
|
||||||
<Head :title="title" />
|
<Head :title="title" />
|
||||||
<ProfileHeader :show="achievementCount && achievementCount > 0 ? 'achievements' : 'flights'" :followStatus="followStatus" :user="user" :flightCount="flightCount" :achievementCount="achievementCount" />
|
<ProfileHeader :showCount="showCount" :show="achievementCount && achievementCount > 0 ? 'achievements' : 'flights'" :followStatus="followStatus" :user="user" :flightCount="flightCount" :achievementCount="achievementCount" />
|
||||||
<div v-if="loading" class="loading-state">
|
<div v-if="loading" class="loading-state">
|
||||||
<PlaneLoader />
|
<PlaneLoader />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+165
-9
@@ -1,8 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MainLayout from "@/Layouts/MainLayout.vue";
|
import MainLayout from "@/Layouts/MainLayout.vue";
|
||||||
import {User, UserAction} from "@/Types/types";
|
import { User, UserAction } from "@/Types/types";
|
||||||
import { Head } from "@inertiajs/vue3";
|
import {Head, Link} from "@inertiajs/vue3";
|
||||||
import FeedItem from "@/Components/FlightsGoneBy/Feed/FeedItem.vue";
|
import FeedItem from "@/Components/FlightsGoneBy/Feed/FeedItem.vue";
|
||||||
|
import ProfileLayout from "@/Components/FlightsGoneBy/ProfileLayout.vue";
|
||||||
|
import FlightMap from "@/Components/FlightsGoneBy/FlightMap.vue";
|
||||||
|
import PlaneLoader from "@/Components/FlightsGoneBy/PlaneLoader.vue";
|
||||||
|
import { useFlights } from "@/Composables/useFlights";
|
||||||
|
import { useApiResource } from "@/Composables/useApiResource";
|
||||||
|
import Avatar from "@/Components/FlightsGoneBy/Feed/Avatar.vue";
|
||||||
|
|
||||||
defineOptions({ layout: MainLayout })
|
defineOptions({ layout: MainLayout })
|
||||||
|
|
||||||
@@ -10,9 +16,16 @@ const props = defineProps<{
|
|||||||
user: User
|
user: User
|
||||||
feed: UserAction[]
|
feed: UserAction[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
// Flights belonging to people the user follows — adjust endpoint to match your API
|
||||||
|
const { flights: followingFlights, flightsLoading } = useFlights('/internal/feed/following-flights')
|
||||||
|
|
||||||
|
// People the user follows — adjust shape/endpoint to match your API
|
||||||
|
const { data: following, loading: followingLoading } = useApiResource<User[]>('/internal/feed/following')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<ProfileLayout :showCount="false" :user="user" followStatus="none" :canView="true" :loading="false">
|
||||||
<Head title="Feed" />
|
<Head title="Feed" />
|
||||||
|
|
||||||
<div class="feed-page">
|
<div class="feed-page">
|
||||||
@@ -21,21 +34,60 @@ const props = defineProps<{
|
|||||||
<span class="feed-count">{{ feed.length }} updates</span>
|
<span class="feed-count">{{ feed.length }} updates</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- MAP: the headline content -->
|
||||||
|
<section class="map-section">
|
||||||
|
<div class="map-caption">
|
||||||
|
<span class="caption-icon">🛫</span>
|
||||||
|
Recently logged by people you follow
|
||||||
|
</div>
|
||||||
|
<FlightMap v-if="!flightsLoading" :flights="followingFlights" :show-legend="false" />
|
||||||
|
<div v-else class="loading-state">
|
||||||
|
<PlaneLoader />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="feed-grid">
|
||||||
|
<!-- FOLLOWING: secondary, supporting content -->
|
||||||
|
<aside class="following-box">
|
||||||
|
<h2>Following</h2>
|
||||||
|
<div v-if="followingLoading" class="following-loading">
|
||||||
|
<PlaneLoader />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!following?.length" class="empty-small">
|
||||||
|
<p>You're not following anyone yet.</p>
|
||||||
|
</div>
|
||||||
|
<ul v-else class="following-list">
|
||||||
|
<li v-for="person in following" :key="person.id" class="following-item">
|
||||||
|
<Avatar :user="person" />
|
||||||
|
<Link :href="route('profile.view', { user: person?.name })">
|
||||||
|
<span class="following-name">{{ person.name }}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- FEED: condensed, de-emphasised -->
|
||||||
|
<div class="feed-condensed">
|
||||||
<div v-if="feed.length === 0" class="empty">
|
<div v-if="feed.length === 0" class="empty">
|
||||||
<p>Nothing here yet — follow someone to see their flight updates!</p>
|
<p>Nothing here yet — follow someone to see their flight updates!</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="feed-list">
|
||||||
<div class="feed-list">
|
<FeedItem v-for="action in feed" :key="action.id" :action="action" compact />
|
||||||
<FeedItem v-for="action in feed" :key="action.id" :action="action" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ProfileLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.feed-page {
|
.feed-page {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
width: 55%;
|
width: 70%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -48,7 +100,6 @@ const props = defineProps<{
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-header h1 {
|
.feed-header h1 {
|
||||||
@@ -62,14 +113,119 @@ const props = defineProps<{
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MAP */
|
||||||
|
.map-section {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-caption {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
z-index: 10;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: rgba(10, 15, 30, 0.65);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 40dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GRID: following sidebar + condensed feed */
|
||||||
|
.feed-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.feed-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-box {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-box h2 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-name {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.following-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-small p {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CONDENSED FEED — de-emphasised vs before */
|
||||||
|
.feed-condensed {
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
.empty p {
|
.empty p {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 3rem;
|
color: #6b7280;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-list {
|
.feed-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2rem;
|
gap: 1rem; /* tightened from 2rem */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const unlocked = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ProfileLayout :title="`${achievement.name}`" :canView="canView" :achievementCount="achievementCount" :user="user" :followStatus="followStatus" :loading="flightsLoading">
|
<ProfileLayout :title="`${achievement.name}`" :canView="canView" :achievementCount="achievementCount" :user="user" :followStatus="followStatus" :loading="flightsLoading" showCount>
|
||||||
<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}`" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import MainLayout from "@/Layouts/MainLayout.vue";
|
|
||||||
import { Head } from '@inertiajs/vue3';
|
|
||||||
import {Flight, User} from "@/Types/types";
|
|
||||||
import ProfileViewSwitcher from "@/Components/FlightsGoneBy/ProfileViewSwitcher.vue";
|
|
||||||
import ProfileLayout from "@/Components/FlightsGoneBy/ProfileLayout.vue";
|
|
||||||
import BoardingPasses from "@/Components/FlightsGoneBy/BoardingPasses.vue";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
layout: MainLayout
|
|
||||||
})
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
user: User
|
|
||||||
flights: Flight[]
|
|
||||||
canEdit: boolean
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Head :title="`${user.name}'s Flights`" />
|
|
||||||
<ProfileLayout :flights="flights" :user="user">
|
|
||||||
<ProfileViewSwitcher :user="user" active-view="passes" />
|
|
||||||
<BoardingPasses :flights="flights" :canEdit="canEdit" />
|
|
||||||
</ProfileLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -71,6 +71,7 @@ const filteredUnlockedCount = computed(() =>
|
|||||||
:loading="false"
|
:loading="false"
|
||||||
:canView="canView"
|
:canView="canView"
|
||||||
:title="`${user.name}'s Achievements`"
|
:title="`${user.name}'s Achievements`"
|
||||||
|
showCount
|
||||||
>
|
>
|
||||||
<ProfileViewSwitcher active-view="achievements" :user="user" />
|
<ProfileViewSwitcher active-view="achievements" :user="user" />
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const props = defineProps<{
|
|||||||
:user="user"
|
:user="user"
|
||||||
:followStatus="followStatus"
|
:followStatus="followStatus"
|
||||||
:flight-count="flightCount"
|
:flight-count="flightCount"
|
||||||
|
showCount
|
||||||
:loading="false">
|
:loading="false">
|
||||||
<Head :title="`${flight.flight_number ?? user.name + '\'s Flight'}`" />
|
<Head :title="`${flight.flight_number ?? user.name + '\'s Flight'}`" />
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ function switchView(view: ProfileView) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ProfileLayout :title="`${user.name}'s Flights`" :canView="canView" :followStatus="followStatus" :flightCount="flightCount" :user="user" :loading="flightsLoading">
|
<ProfileLayout :title="`${user.name}'s Flights`" :canView="canView" :followStatus="followStatus" :flightCount="flightCount" :user="user" :loading="flightsLoading" showCount>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<ProfileViewSwitcher :user="user" :active-view="activeView" @update:active-view="switchView" />
|
<ProfileViewSwitcher :user="user" :active-view="activeView" @update:active-view="switchView" />
|
||||||
|
|||||||
Vendored
+1
@@ -300,6 +300,7 @@ export interface Flight {
|
|||||||
range: FlightRange
|
range: FlightRange
|
||||||
region_range: RegionRange
|
region_range: RegionRange
|
||||||
livery_url?: string
|
livery_url?: string
|
||||||
|
user?: User | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -3,6 +3,7 @@
|
|||||||
use App\Http\Controllers\Api\AircraftApiController;
|
use App\Http\Controllers\Api\AircraftApiController;
|
||||||
use App\Http\Controllers\Api\AirlineApiController;
|
use App\Http\Controllers\Api\AirlineApiController;
|
||||||
use App\Http\Controllers\Api\UserApiController;
|
use App\Http\Controllers\Api\UserApiController;
|
||||||
|
use App\Http\Controllers\FeedController;
|
||||||
use App\Http\Controllers\HomePageController;
|
use App\Http\Controllers\HomePageController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
@@ -16,7 +17,11 @@ Route::domain(config('app.api_domain'))->group(function () {
|
|||||||
Route::prefix('internal')->middleware('sanctum.or.trusted')->group(function () {
|
Route::prefix('internal')->middleware('sanctum.or.trusted')->group(function () {
|
||||||
Route::get('/user/{user}/flights', [UserApiController::class, 'viewableFlights'])->name('api.user.flights');
|
Route::get('/user/{user}/flights', [UserApiController::class, 'viewableFlights'])->name('api.user.flights');
|
||||||
Route::get('/user/{user}/flights/departed', [UserApiController::class, 'viewableDepartedFlights'])->name('api.user.flights.departed');
|
Route::get('/user/{user}/flights/departed', [UserApiController::class, 'viewableDepartedFlights'])->name('api.user.flights.departed');
|
||||||
Route::get('/flights/most-recent', [HomePageController::class ,'mostRecentFlights'])->name('api.user.flights.departed');
|
Route::get('/flights/most-recent', [HomePageController::class ,'mostRecentFlights'])->name('home-page.most-recent-flights');
|
||||||
|
Route::get('/feed/following', [FeedController::class ,'following'])->name('feed.following');
|
||||||
|
Route::get('/feed/following-flights', [FeedController::class ,'followingFlights'])->name('feed.following-flights');
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-28
@@ -1,37 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AchievementController;
|
|
||||||
use App\Http\Controllers\AdminController;
|
use App\Http\Controllers\AdminController;
|
||||||
use App\Http\Controllers\AdminToolsController;
|
|
||||||
use App\Http\Controllers\Api\AircraftApiController;
|
|
||||||
use App\Http\Controllers\Api\AirlineApiController;
|
|
||||||
use App\Http\Controllers\Api\UserApiController;
|
|
||||||
use App\Http\Controllers\FeedController;
|
use App\Http\Controllers\FeedController;
|
||||||
use App\Http\Controllers\FlightController;
|
use App\Http\Controllers\FlightController;
|
||||||
use App\Http\Controllers\FlightImportController;
|
use App\Http\Controllers\FlightImportController;
|
||||||
use App\Http\Controllers\FollowerController;
|
use App\Http\Controllers\FollowerController;
|
||||||
use App\Http\Controllers\HomePageController;
|
use App\Http\Controllers\HomePageController;
|
||||||
use App\Http\Controllers\UserProfileController;
|
use App\Http\Controllers\UserProfileController;
|
||||||
use App\Http\Controllers\LogoController;
|
|
||||||
use App\Http\Controllers\NotificationController;
|
use App\Http\Controllers\NotificationController;
|
||||||
use App\Http\Controllers\ProfileController;
|
|
||||||
use App\Http\Controllers\SearchController;
|
use App\Http\Controllers\SearchController;
|
||||||
use App\Http\Controllers\SettingsController;
|
use App\Http\Controllers\SettingsController;
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
use App\Http\Controllers\UserFlightController;
|
|
||||||
use App\Models\Airline;
|
|
||||||
use App\Models\FlightClass;
|
|
||||||
use App\Models\FlightReason;
|
|
||||||
use App\Models\SeatType;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App Routes
|
* App Routes
|
||||||
*/
|
*/
|
||||||
Route::domain(config('app.domain'))->group(
|
|
||||||
function() {
|
|
||||||
Route::get('/', [UserProfileController::class, 'index'])->name('home');
|
Route::get('/', [UserProfileController::class, 'index'])->name('home');
|
||||||
|
|
||||||
Route::get('/splash', [HomePageController::class, 'splash'])->name('splash');
|
Route::get('/splash', [HomePageController::class, 'splash'])->name('splash');
|
||||||
@@ -65,7 +50,6 @@ Route::domain(config('app.domain'))->group(
|
|||||||
Route::get('/flights/lookup', [FlightController::class, 'lookup'])->name('flights.lookup');
|
Route::get('/flights/lookup', [FlightController::class, 'lookup'])->name('flights.lookup');
|
||||||
Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store');
|
Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store');
|
||||||
|
|
||||||
Route::post('/u/{user}/follow', [UserController::class, 'follow'])->name('profile.follow');
|
|
||||||
|
|
||||||
Route::get('/settings/{category?}', [UserController::class, 'settings'])->name('user.settings');
|
Route::get('/settings/{category?}', [UserController::class, 'settings'])->name('user.settings');
|
||||||
|
|
||||||
@@ -77,6 +61,7 @@ Route::domain(config('app.domain'))->group(
|
|||||||
|
|
||||||
Route::patch('/settings', [SettingsController::class, 'update'])->name('settings.update');
|
Route::patch('/settings', [SettingsController::class, 'update'])->name('settings.update');
|
||||||
|
|
||||||
|
Route::post('/u/{user}/follow', [FollowerController::class, 'follow'])->name('profile.follow');
|
||||||
Route::get('/followers', [FollowerController::class, 'index'])->name('followers.index');
|
Route::get('/followers', [FollowerController::class, 'index'])->name('followers.index');
|
||||||
Route::post('/followers/{follower}/approve', [FollowerController::class, 'approve'])->name('followers.approve');
|
Route::post('/followers/{follower}/approve', [FollowerController::class, 'approve'])->name('followers.approve');
|
||||||
Route::post('/followers/{follower}/deny', [FollowerController::class, 'deny'])->name('followers.deny');
|
Route::post('/followers/{follower}/deny', [FollowerController::class, 'deny'])->name('followers.deny');
|
||||||
@@ -102,13 +87,3 @@ Route::domain(config('app.domain'))->group(
|
|||||||
|
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API Routes
|
|
||||||
*/
|
|
||||||
Route::domain(config('app.api_domain'))->group(function () {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user