# Architecture Server-rendered Laravel + Livewire app (no SPA/API-driven frontend for the app itself — the REST API in `routes/api.php` exists purely for external integrations). See [README.md](README.md) for the feature list and tech stack; this doc covers how the pieces fit together. ## Request flow 1. `routes/web.php` gates every area behind `auth` + a role middleware (`role:client`, `role:operator`, `role:admin` → `App\Http\Middleware\EnsureRole`), which checks the role against `$user->roles`. A user can hold multiple roles at once; the router just requires *one* of the listed roles per route group. 2. Each route resolves to a full-page Livewire component under `app/Livewire/{Client,Operator,Admin,Auth}/` — there are no traditional controllers rendering Blade views for these areas (the REST API in `routes/api.php` is the exception, backed by `app/Http/Controllers/Api/`). 3. Livewire components call into `app/Services/TicketService.php` for anything that mutates ticket state (create/transition/reply/notify) rather than mutating models directly — keep that convention when adding new mutations so notification/history/SLA side effects stay in one place. 4. `App\Providers\AppServiceProvider::boot()` runs a settings override pass on every request (`applyLdapSettingsOverride`, `applyMailSettingsOverride`, `applySessionSettingsOverride`, `applyTimezoneSettingsOverride`) — see "Settings override" below. ## Data model Core tables/models (`app/Models/`): ``` Category ─< Subcategory ─< CustomField (per-subcategory custom fields) │ └──< Ticket >── Team (subcategory routes to a team) │ ├──< TicketMessage (public replies + internal notes) ├──< TicketAttachment ├──< TicketHistory ├── customer/assignee → User ├── status → Status (fixed stages: new/open/closed) └── priority → Priority → SlaRule (response/resolution minutes) User ─< UserFieldValue >─ UserField ApiClient (Sanctum token owner, ability-scoped) Setting (single-row-per-key config store, see below) ReplyQuickAction, ResponseTemplate, EmailTemplate, NotificationSetting ``` `Ticket` (`app/Models/Ticket.php`) is the largest model — it owns SLA math (`slaInfo()`, `isOverdue()`, `resolutionDeadline()`), status/priority display helpers (`statusLabel()`, `tagStyleFromColor()`), operator-visibility scoping (`scopeVisibleToOperator`, `isVisibleToOperator` — a team member sees their team's queue + unassigned + anything assigned to them, an admin sees everything), and work-timer tracking (`timerElapsedSeconds()`). Keep ticket-shaped logic here rather than spreading it across Livewire components. ## Roles & permissions Roles are a plain array on the user (`$user->roles`), not a separate pivot-backed package — checked via `EnsureRole` at the route level. Every account gets `client` by default (`App\Ldap\Handlers\AssignDefaultRole` for LDAP-provisioned accounts); staff switch areas via the header role switcher, but always land on `/client` first after login. ## Authentication `config/auth.php` defines the default `web` guard against an LDAP-backed user provider (LdapRecord); a plain Eloquent provider is kept alongside it only for local tooling/tests that don't hit a directory. In production, LDAP bind is the primary path; the local fallback account (`admin@example.com` from the seeder) authenticates against a local password when the LDAP bind doesn't match — this is the account used for first login after a fresh install (see [install.md](install.md)). `app/Ldap/Handlers/` hooks into LdapRecord's import/sync events: `AssignDefaultRole` grants the `client` role to new LDAP-provisioned accounts, `SyncUserFieldsFromLdap` keeps `UserFieldValue` rows in sync with directory attributes. ## Settings override ("live config") `App\Support\Settings` (`app/Support/Settings.php`) is a cached key/value reader over the `settings` table, with hardcoded defaults for every key (company name, LDAP/SMTP connection details, attachment limits, session lifetime, timezone, branding/email HTML, etc.). Admin > Konfiguracja writes to this table, and `AppServiceProvider::boot()` re-applies the relevant subset of it over `config()` on every request — meaning **`Setting` rows win over `.env`** for LDAP, mail, session lifetime and timezone once they're non-empty. This is by design (lets an admin reconfigure LDAP/SMTP without a redeploy) but is also the source of the "seeded placeholder overrides real `.env` values" gotcha documented in [install.md](install.md) — anything touching LDAP/mail/session/ timezone config should go through `Settings`, not raw `config()`/`.env` reads. ## SLA `SlaRule` holds per-priority response/resolution targets in minutes. The scheduled command `tickets:check-sla-breaches` (registered in `routes/console.php`, run every 15 minutes via `schedule:run`) flags overdue tickets and can notify the assigned operator — see [install.md](install.md) for why this requires an external cron entry (the Docker image ships no cron/supervisor of its own). ## API `routes/api.php` + `app/Http/Controllers/Api/` expose a small ability-scoped REST surface over Sanctum tokens (`tickets:read`, `tickets:write`, `dictionaries:read`, `users:read`), issued via admin-managed `ApiClient` records. Rate limiting is configured per-client (120 req/min keyed by client ID) vs. a tighter per-IP limit for unauthenticated requests (`AppServiceProvider::configureApiRateLimiting()`). Interactive docs are generated by L5-Swagger at `/admin/api-docs`; there is no static Markdown API reference in-repo.