- E-mail intake (IMAP), optional and off by default: clients can create a ticket or reply to an existing one just by sending/replying to an e-mail. Configure any number of mailboxes in the new Admin > Poczta page (SMTP + IMAP together, replacing the old "E-MAIL" tab), each routed to a specific subcategory or a whole category (new tickets.category_id column). Replies are matched to their ticket via the number/checksum already in every notification subject; autoresponders/bounces are detected and rejected; "restrict tickets to LDAP" is enforced for e-mail like the guest web form. Manual "Pobierz teraz" per-mailbox fetch button; dedicated storage/logs/imap-*.log regardless of the app's log level; mail-icon badges on e-mail-originated tickets/messages in the operator queue and ticket view. - Operator queue: "select all" checkbox in the table header for every currently visible ticket under the active filter/tab. - Fixed: scheduled commands (SLA breach check, automation rules, and now IMAP fetch) always sent notifications through .env's default mailer instead of the configured SMTP server, because AppServiceProvider's Settings override used to skip itself for any console command, not just migrate. - Fixed: visiting a ticket that no longer exists (deleted mid-session, or a stale background refresh) showed a raw 404 instead of redirecting back to the operator queue / client dashboard. - Docs: README/ARCHITECTURE/CLAUDE/install/wiki updated for all of the above, including the previously-missing host crontab entry for schedule:run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
3.4 KiB
PHP
75 lines
3.4 KiB
PHP
<?php
|
|
|
|
use App\Http\Middleware\EnsureRole;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Foundation\Application;
|
|
use Illuminate\Foundation\Configuration\Exceptions;
|
|
use Illuminate\Foundation\Configuration\Middleware;
|
|
use Illuminate\Http\Request;
|
|
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
|
|
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
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 {
|
|
// The app only ever receives traffic from the Traefik reverse proxy on the
|
|
// internal docker network (TLS is terminated there), so trust its
|
|
// X-Forwarded-* headers — otherwise Laravel thinks every request is plain
|
|
// HTTP and generates http:// asset/URL links, which browsers block as
|
|
// mixed content on the https:// site.
|
|
$middleware->trustProxies(at: '*', headers: Request::HEADER_X_FORWARDED_FOR |
|
|
Request::HEADER_X_FORWARDED_HOST |
|
|
Request::HEADER_X_FORWARDED_PORT |
|
|
Request::HEADER_X_FORWARDED_PROTO |
|
|
Request::HEADER_X_FORWARDED_AWS_ELB);
|
|
|
|
$middleware->alias([
|
|
'role' => EnsureRole::class,
|
|
'abilities' => CheckAbilities::class,
|
|
'ability' => CheckForAnyAbility::class,
|
|
]);
|
|
|
|
// navigator.sendBeacon (used to stop the ticket timer on tab close,
|
|
// see routes/web.php) can't attach a CSRF header/field.
|
|
$middleware->validateCsrfTokens(except: [
|
|
'operator/tickets/*/stop-timer',
|
|
]);
|
|
})
|
|
->withExceptions(function (Exceptions $exceptions): void {
|
|
$exceptions->shouldRenderJsonWhen(
|
|
fn (Request $request) => $request->is('api/*'),
|
|
);
|
|
|
|
// A ticket deleted mid-session (typically by the operator/client
|
|
// currently viewing it) leaves any later request for that same
|
|
// {ticket} route binding 404ing — most commonly Livewire's own
|
|
// "model missing during hydration" recovery, which does a full
|
|
// window.location.reload() of the very page whose ticket just
|
|
// disappeared (e.g. the ticket-show view's periodic fallback
|
|
// refresh polling a few seconds after a delete+redirect). Land back
|
|
// on that area's own list page instead of a raw 404.
|
|
//
|
|
// Handler::prepareException() already converts ModelNotFoundException
|
|
// into NotFoundHttpException (wrapping the original as getPrevious())
|
|
// before any render() callback is dispatched — a callback typed
|
|
// against ModelNotFoundException itself would simply never match.
|
|
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
|
|
if (! $e->getPrevious() instanceof ModelNotFoundException || ! $request->user()) {
|
|
return null;
|
|
}
|
|
|
|
return match (true) {
|
|
$request->is('operator/*') => redirect()->route('operator.queue'),
|
|
$request->is('client/*') => redirect()->route('client.dashboard'),
|
|
default => null,
|
|
};
|
|
});
|
|
})->create();
|