163 lines
8.2 KiB
PHP
163 lines
8.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* ============================================================
|
|
* @project biiproject
|
|
* @author Andika Debi Putra
|
|
* @email andikadebiputra@gmail.com
|
|
* @website https://biiproject.com
|
|
* @copyright Copyright (c) 2026 Andika Debi Putra
|
|
* @license Proprietary - All Rights Reserved
|
|
* @version 1.0.0
|
|
* @created 2026-05-01
|
|
* ============================================================
|
|
*/
|
|
|
|
use Illuminate\Foundation\Application;
|
|
use Illuminate\Foundation\Configuration\Exceptions;
|
|
use Illuminate\Foundation\Configuration\Middleware;
|
|
use Spatie\Permission\Middleware\RoleMiddleware;
|
|
use Spatie\Permission\Middleware\PermissionMiddleware;
|
|
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
|
|
use App\Http\Middleware\CheckActivePermission;
|
|
|
|
return Application::configure(basePath: dirname(__DIR__))
|
|
->withRouting(
|
|
web: __DIR__.'/../routes/web.php',
|
|
api: __DIR__.'/../routes/api.php',
|
|
commands: __DIR__.'/../routes/console.php',
|
|
channels: __DIR__.'/../routes/channels.php',
|
|
health: '/up',
|
|
)
|
|
->withMiddleware(function (Middleware $middleware): void {
|
|
// GLOBAL MIDDLEWARE
|
|
$middleware->append(\App\Http\Middleware\GzipCompression::class);
|
|
|
|
// REGISTER ALIAS MIDDLEWARE
|
|
$middleware->alias([
|
|
'role' => RoleMiddleware::class,
|
|
'permission' => PermissionMiddleware::class,
|
|
'role_or_permission' => RoleOrPermissionMiddleware::class,
|
|
|
|
// CUSTOM MIDDLEWARE (CEK PERMISSION AKTIF)
|
|
'active-permission' => CheckActivePermission::class,
|
|
'password-expiry' => \App\Http\Middleware\PasswordExpiryMiddleware::class,
|
|
'check-legal' => \App\Http\Middleware\CheckLegalAgreement::class,
|
|
'mobile-guard' => \App\Http\Middleware\MobileMaintenanceMiddleware::class,
|
|
'tab-permission' => \App\Http\Middleware\CheckTabPermission::class,
|
|
'menu-permission' => \App\Http\Middleware\CheckMenuPermission::class,
|
|
]);
|
|
|
|
$middleware->web(append: [
|
|
\App\Http\Middleware\SecurityHeaders::class,
|
|
\App\Http\Middleware\IpAccessControl::class,
|
|
'password-expiry',
|
|
'check-legal',
|
|
]);
|
|
|
|
})
|
|
->withSchedule(function (\Illuminate\Console\Scheduling\Schedule $schedule): void {
|
|
// Dikelola di routes/console.php
|
|
})
|
|
->withExceptions(function (Exceptions $exceptions): void {
|
|
$exceptions->report(function (\Throwable $e) {
|
|
// --- AI SELF HEALING INTERCEPTOR ---
|
|
if (!($e instanceof \Symfony\Component\HttpKernel\Exception\HttpException)) {
|
|
try {
|
|
// Re-entrancy guard: if the AI healer itself is currently running,
|
|
// never re-enter (prevents infinite loops if healing trips an error).
|
|
if (defined('AI_HEALER_RUNNING')) {
|
|
return;
|
|
}
|
|
|
|
$isAiHealingEnabled = false;
|
|
$isAiProviderEnabled = false;
|
|
try {
|
|
$isAiHealingEnabled = app(\App\Services\SystemConfig\SystemConfigService::class)
|
|
->get('ai_healing_enabled', false);
|
|
$isAiProviderEnabled = app(\App\Services\SystemConfig\SystemConfigService::class)
|
|
->get('ai_enabled', false);
|
|
} catch (\Throwable $t) {}
|
|
|
|
if (!$isAiHealingEnabled || !$isAiProviderEnabled) {
|
|
// Healer or Provider disabled — fall through to Sentry below.
|
|
} else {
|
|
// Skip exceptions originating *from* the healing pipeline itself
|
|
// OR from critical files we never want the healer to touch.
|
|
$traceStr = $e->getFile() . $e->getTraceAsString();
|
|
$msg = (string) $e->getMessage();
|
|
$isInternalHealerError = str_contains($traceStr, 'AiHealerJob')
|
|
|| str_contains($traceStr, 'AiHealingLog')
|
|
|| str_contains($traceStr, 'AiSelfHealingController')
|
|
|| str_contains($traceStr, 'app/Services/AI/')
|
|
// Self-exclusion: never let the healer try to edit these files.
|
|
|| str_contains($msg, 'ai-self-healing.blade.php')
|
|
|| str_contains($msg, 'navigation.blade.php')
|
|
|| str_contains($msg, 'layouts/app.blade.php')
|
|
|| str_contains($traceStr, 'ai-self-healing.blade.php')
|
|
|| str_contains($traceStr, 'navigation.blade.php');
|
|
|
|
if (!$isInternalHealerError) {
|
|
// Dedup: same error_type already pending OR diagnosing.
|
|
// Diagnosing logs older than 5 min are considered stuck and reset to pending.
|
|
\App\Models\AiHealingLog::where('error_type', get_class($e))
|
|
->where('status', 'diagnosing')
|
|
->where('updated_at', '<', now()->subMinutes(5))
|
|
->update(['status' => 'pending', 'action_taken' => 'Auto-reset from stuck diagnosing state']);
|
|
|
|
$duplicate = \App\Models\AiHealingLog::where('error_type', get_class($e))
|
|
->whereIn('status', ['pending', 'diagnosing'])
|
|
->where('created_at', '>=', now()->subMinutes(2))
|
|
->exists();
|
|
|
|
if (!$duplicate) {
|
|
// --- CIRCUIT BREAKER ---
|
|
$maxPerHour = (int) app(\App\Services\SystemConfig\SystemConfigService::class)
|
|
->get('ai_healing_max_attempts_per_hour', 5);
|
|
|
|
$recentCount = \App\Models\AiHealingLog::where('created_at', '>', now()->subHour())
|
|
->whereIn('status', ['resolved', 'pending', 'diagnosing'])
|
|
->count();
|
|
|
|
if ($recentCount >= $maxPerHour) {
|
|
// Circuit breaker tripped! Only log to standard Laravel log, don't trigger AI.
|
|
\Illuminate\Support\Facades\Log::warning("AI Healer Circuit Breaker tripped. Total fixes in last hour: {$recentCount}. Threshold: {$maxPerHour}");
|
|
return;
|
|
}
|
|
|
|
define('AI_HEALER_RUNNING', true);
|
|
|
|
$log = \App\Models\AiHealingLog::create([
|
|
'error_type' => get_class($e),
|
|
'error_message' => $e->getMessage(),
|
|
'stack_trace' => $e->getTraceAsString(),
|
|
'status' => 'pending',
|
|
]);
|
|
|
|
try {
|
|
\App\Jobs\AiHealerJob::dispatchSync(
|
|
$log->id,
|
|
$e->getFile(),
|
|
$e->getLine()
|
|
);
|
|
} catch (\Throwable $inner) {
|
|
$log->update([
|
|
'status' => 'failed',
|
|
'ai_diagnosis' => 'Healer dispatch crashed: ' . $inner->getMessage(),
|
|
'action_taken' => 'Pipeline error',
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $err) {
|
|
// Silently ignore — never let the interceptor break error reporting.
|
|
}
|
|
}
|
|
|
|
if (app()->bound('sentry') && app()->environment('production', 'staging')) {
|
|
app('sentry')->captureException($e);
|
|
}
|
|
});
|
|
})
|
|
->create(); |