feat(v5): add Inertia app shell with Flux health

Adds v5 routing, middleware, home page rendering, team context sharing,
project model/table support, and Flux health reporting. Installs Flux in
container builds with role-aware s6 services and documents runtime roles.
This commit is contained in:
Andras Bacsai committed 2026-06-15 23:09:49 +02:00
1 parent 02ab4b39ef
commit dcd325dc44
41 files changed
+1705 -108

No files matched your search

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\V5;
use App\Http\Controllers\Controller;
use App\Models\Team;
use App\Models\User;
use App\Services\Flux\FluxHealth;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class HomeController extends Controller
{
public function __invoke(Request $request, FluxHealth $fluxHealth): Response
{
/** @var User $user */
$user = $request->user();
$currentTeam = $request->attributes->get('v5.currentTeam');
return Inertia::render('Home', [
'status' => 'v5-ready',
'flux' => $fluxHealth->check(),
'currentTeam' => $currentTeam instanceof Team ? [
'id' => $currentTeam->id,
'name' => $currentTeam->name,
'description' => $currentTeam->description,
'role' => $currentTeam->pivot?->role ?? $user->roleInTeam($currentTeam->id),
'personal' => $currentTeam->personal_team,
] : null,
'teams' => $user->teams()
->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team')
->orderBy('teams.name')
->get()
->map(fn (Team $team) => [
'id' => $team->id,
'name' => $team->name,
'description' => $team->description,
'role' => $team->pivot->role,
'personal' => $team->personal_team,
]),
]);
}
}
+18
View File
@@ -18,6 +18,8 @@ use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustHosts;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\V5\EnsureCurrentTeam as V5EnsureCurrentTeam;
use App\Http\Middleware\V5\HandleInertiaRequests as V5HandleInertiaRequests;
use App\Http\Middleware\ValidateSignature;
use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
@@ -76,6 +78,22 @@ class Kernel extends HttpKernel
],
'v5.web' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
V5HandleInertiaRequests::class,
],
'v5.authenticated' => [
'auth',
'verified',
V5EnsureCurrentTeam::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
ThrottleRequests::class.':api',
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Middleware\V5;
use App\Models\Team;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureCurrentTeam
{
public function handle(Request $request, Closure $next): Response
{
/** @var User|null $user */
$user = $request->user();
if (! $user) {
return $next($request);
}
$currentTeam = $this->resolveCurrentTeam($user);
if (! $currentTeam) {
abort(403, 'No team available for this user.');
}
session(['currentTeam' => $currentTeam]);
$request->attributes->set('v5.currentTeam', $currentTeam);
return $next($request);
}
private function resolveCurrentTeam(User $user): ?Team
{
$sessionTeamId = data_get(session('currentTeam'), 'id');
if ($sessionTeamId) {
$sessionTeam = $user->teams()
->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team')
->whereKey($sessionTeamId)
->first();
if ($sessionTeam) {
return $sessionTeam;
}
}
return $user->teams()
->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team')
->orderBy('teams.id')
->first();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware\V5;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
protected $rootView = 'v5.app';
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
];
}
}