Fix 500 error on admin access: make navigation layout resilient and auto-run pending migrations on admin entry

This commit is contained in:
2026-11-02 10:38:22 +01:00
parent 44c440222a
commit cf55f3c9f9
7 changed files with 76 additions and 9 deletions
@@ -8,6 +8,7 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\View\View; use Illuminate\View\View;
@@ -26,11 +27,14 @@ class AccountController extends Controller
->latest() ->latest()
->get(); ->get();
$communications = collect();
if (Schema::hasTable('communications')) {
$communications = Communication::where('user_id', $user->id) $communications = Communication::where('user_id', $user->id)
->orWhere('email', strtolower($user->email)) ->orWhere('email', strtolower($user->email))
->with('replies') ->with('replies')
->latest() ->latest()
->get(); ->get();
}
return view('account.index', compact('user', 'orders', 'communications')); return view('account.index', compact('user', 'orders', 'communications'));
} }
@@ -8,9 +8,11 @@ use App\Models\Communication;
use App\Models\CommunicationReply; use App\Models\CommunicationReply;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View; use Illuminate\View\View;
class CommunicationController extends Controller class CommunicationController extends Controller
@@ -20,6 +22,10 @@ class CommunicationController extends Controller
*/ */
public function index(Request $request): View public function index(Request $request): View
{ {
if (! Schema::hasTable('communications')) {
Artisan::call('migrate', ['--force' => true]);
}
$query = Communication::with(['user', 'order', 'replies'])->latest(); $query = Communication::with(['user', 'order', 'replies'])->latest();
// Status filter // Status filter
@@ -8,6 +8,8 @@ use App\Models\Order;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View; use Illuminate\View\View;
class CustomerController extends Controller class CustomerController extends Controller
@@ -17,6 +19,10 @@ class CustomerController extends Controller
*/ */
public function index(Request $request): View public function index(Request $request): View
{ {
if (! Schema::hasTable('communications')) {
Artisan::call('migrate', ['--force' => true]);
}
$search = trim($request->input('q', '')); $search = trim($request->input('q', ''));
// Query registered non-admin users // Query registered non-admin users
@@ -9,9 +9,11 @@ use App\Models\Order;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View; use Illuminate\View\View;
class ContactController extends Controller class ContactController extends Controller
@@ -60,6 +62,10 @@ class ContactController extends Controller
} }
} }
if (! Schema::hasTable('communications')) {
Artisan::call('migrate', ['--force' => true]);
}
$communication = Communication::create([ $communication = Communication::create([
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'order_id' => $orderId, 'order_id' => $orderId,
+13
View File
@@ -4,6 +4,9 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsAdmin class EnsureUserIsAdmin
@@ -23,6 +26,16 @@ class EnsureUserIsAdmin
->with('error', 'Je hebt geen beheerderstoegang tot deze pagina.'); ->with('error', 'Je hebt geen beheerderstoegang tot deze pagina.');
} }
// Auto-migrate check: Ensure that new database tables (like communications) are migrated
// automatically when an admin enters the admin panel, avoiding 500 errors on Coolify/production.
try {
if (! Schema::hasTable('communications')) {
Artisan::call('migrate', ['--force' => true]);
}
} catch (\Throwable $e) {
Log::warning('Auto-migration in EnsureUserIsAdmin failed: '.$e->getMessage());
}
return $next($request); return $next($request);
} }
} }
+22 -1
View File
@@ -51,7 +51,14 @@
</a> </a>
@php @php
$draftBadgeCount = 0;
try {
if (\Illuminate\Support\Facades\Schema::hasTable('products')) {
$draftBadgeCount = \App\Models\Product::where('status', 'draft')->count(); $draftBadgeCount = \App\Models\Product::where('status', 'draft')->count();
}
} catch (\Throwable $e) {
$draftBadgeCount = 0;
}
@endphp @endphp
<a href="{{ route('admin.makerworld.staging') }}" class="px-3 py-1.5 rounded-lg transition flex items-center space-x-1.5 {{ request()->routeIs('admin.makerworld.staging*') ? 'bg-slate-900 text-white font-semibold' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100' }}"> <a href="{{ route('admin.makerworld.staging') }}" class="px-3 py-1.5 rounded-lg transition flex items-center space-x-1.5 {{ request()->routeIs('admin.makerworld.staging*') ? 'bg-slate-900 text-white font-semibold' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100' }}">
<i data-lucide="layers" class="w-3.5 h-3.5"></i> <i data-lucide="layers" class="w-3.5 h-3.5"></i>
@@ -74,7 +81,14 @@
</a> </a>
@php @php
$unreadCommCount = 0;
try {
if (\Illuminate\Support\Facades\Schema::hasTable('communications')) {
$unreadCommCount = \App\Models\Communication::where('status', 'new')->count(); $unreadCommCount = \App\Models\Communication::where('status', 'new')->count();
}
} catch (\Throwable $e) {
$unreadCommCount = 0;
}
@endphp @endphp
<a href="{{ route('admin.communications.index') }}" class="px-3 py-1.5 rounded-lg transition flex items-center space-x-1.5 {{ request()->routeIs('admin.communications.*') ? 'bg-slate-900 text-white font-semibold' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100' }}"> <a href="{{ route('admin.communications.index') }}" class="px-3 py-1.5 rounded-lg transition flex items-center space-x-1.5 {{ request()->routeIs('admin.communications.*') ? 'bg-slate-900 text-white font-semibold' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100' }}">
<i data-lucide="message-square" class="w-3.5 h-3.5"></i> <i data-lucide="message-square" class="w-3.5 h-3.5"></i>
@@ -101,7 +115,14 @@
<!-- Right Actions --> <!-- Right Actions -->
<div class="flex items-center space-x-2.5"> <div class="flex items-center space-x-2.5">
@php @php
$isShopOffline = false;
try {
if (\Illuminate\Support\Facades\Schema::hasTable('settings')) {
$isShopOffline = \App\Models\Setting::get('shop_offline', '0') === '1'; $isShopOffline = \App\Models\Setting::get('shop_offline', '0') === '1';
}
} catch (\Throwable $e) {
$isShopOffline = false;
}
@endphp @endphp
<!-- Webshop Offline / Online Status Switcher --> <!-- Webshop Offline / Online Status Switcher -->
@@ -175,7 +196,7 @@
</header> </header>
<!-- Offline Alert Banner --> <!-- Offline Alert Banner -->
@if($isShopOffline ?? (\App\Models\Setting::get('shop_offline', '0') === '1')) @if(!empty($isShopOffline))
<div class="bg-rose-500 text-white px-4 py-2.5 text-xs font-medium border-b border-rose-600 shadow-sm flex flex-col sm:flex-row items-center justify-between gap-3 sticky top-16 z-20"> <div class="bg-rose-500 text-white px-4 py-2.5 text-xs font-medium border-b border-rose-600 shadow-sm flex flex-col sm:flex-row items-center justify-between gap-3 sticky top-16 z-20">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full bg-white animate-pulse shrink-0"></span> <span class="w-2 h-2 rounded-full bg-white animate-pulse shrink-0"></span>
@@ -10,6 +10,7 @@ use App\Models\Order;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase; use Tests\TestCase;
class CustomerCommunicationTest extends TestCase class CustomerCommunicationTest extends TestCase
@@ -231,4 +232,14 @@ class CustomerCommunicationTest extends TestCase
$this->actingAs($this->customer)->get(route('admin.communications.index'))->assertRedirect(route('home')); $this->actingAs($this->customer)->get(route('admin.communications.index'))->assertRedirect(route('home'));
$this->actingAs($this->customer)->get(route('admin.customers.index'))->assertRedirect(route('home')); $this->actingAs($this->customer)->get(route('admin.customers.index'))->assertRedirect(route('home'));
} }
public function test_admin_layout_does_not_crash_even_if_communications_table_is_initially_missing(): void
{
Schema::dropIfExists('communication_replies');
Schema::dropIfExists('communications');
// Layout renders with HTTP 200 without 500 server error even when table was absent
$response = $this->actingAs($this->admin)->get(route('admin.content.index'));
$response->assertStatus(200);
}
} }