2
Arsitektur Aplikasi
Aplikasi ini adalah satu aplikasi Laravel dengan dua permukaan terpisah: panel admin Filament di path admin, dan portal eksternal berbasis komponen Livewire. Keduanya berbagi model domain, policy, dan layer pendukung yang sama. Tiga service provider didaftarkan secara eksplisit, yaitu AppServiceProvider, EventServiceProvider, dan AdminPanelProvider (bootstrap/providers.php:1-71). Routing web dimuat dari routes/web.php melalui konfigurasi aplikasi di bootstrap/app.php:7-122.
Peta lapisan
Diagram berikut menunjukkan bagaimana routes/web.php membelah trafik ke panel admin, portal eksternal, dan dua route autentikasi Google, lalu bagaimana tiap cabang menyentuh model domain, policy, dan layer pendukung.
graph TD
REQ["HTTP request"] --> ROUTES["routes/web.php"]
ROUTES --> WELCOME["closure welcome"]
ROUTES --> GOOGLE["GoogleController"]
ROUTES --> EXTLOGIN["ExternalLogin"]
ROUTES --> EXTDASH["ExternalDashboard"]
ROUTES --> PANEL["AdminPanelProvider panel admin /admin"]
subgraph ADMIN["Admin panel"]
PANEL --> MW["FilamentUserSettings"]
MW --> SETTING["Setting::getUserValue"]
MW --> PALETTE["ColorPalette::constantFor"]
PANEL --> RES["Filament Resources dan Pages"]
RES --> POLICY["TicketPolicy"]
end
subgraph EXTERNAL["External portal"]
EXTLOGIN --> EA["ExternalAccess"]
EXTDASH --> EA
end
subgraph SUPPORT["Supporting layer"]
EVENT["ProjectMemberAttached dan ProjectMemberDetached"] --> LISTENER["SendProjectAssignmentNotification dan SendProjectRemovalNotification"]
NS["NotificationService"] --> NOTIF["Notification"]
end
POLICY --> MODELS["Eloquent models"]
EA --> MODELS
NOTIF --> MODELS
MODELS --> DB[("MySQL")]
style DB fill:#336791,color:#fff
style MW fill:#f59e0b,color:#000
style SETTING fill:#f59e0b,color:#000
style POLICY fill:#8b5cf6,color:#fff
style EA fill:#f59e0b,color:#000
%% fig: Peta lapisan aplikasi
%% desc: Satu berkas route membelah trafik ke panel admin Filament, portal eksternal Livewire, dan autentikasi Google, lalu semuanya bermuara ke model Eloquent dan database.Panel admin didaftarkan lewat PanelProvider dengan ->default(), ->id('admin'), dan ->path('admin'), serta mengaktifkan mode SPA dan pembungkusan transaksi database (app/Providers/Filament/AdminPanelProvider.php:25-433). Resource, Page, dan Widget tidak didaftarkan satu per satu, melainkan ditemukan otomatis dari direktori app/Filament/Resources, app/Filament/Pages, dan app/Filament/Widgets.
Di atas panel itu terpasang rantai middleware EncryptCookies, StartSession, AuthenticateSession, VerifyCsrfToken, SubstituteBindings, hingga FilamentUserSettings, ditambah plugin FilamentShieldPlugin, ->passwordReset(), ->emailVerification(), ->profile(), dan tema Vite resources/css/filament/admin/theme.css (app/Providers/Filament/AdminPanelProvider.php:44-664).
Middleware pengaturan per pengguna
FilamentUserSettings adalah middleware yang menyentuh database pada setiap request panel. Ketika auth()->check() bernilai benar, middleware membaca warna primer tersimpan lewat Setting::getUserValue('filament_primary_color', 'blue', $userId), mendaftarkannya ke FilamentColor::register, lalu mengatur gaya navigasi (app/Http/Middleware/FilamentUserSettings.php:15-295). Nilai filament_navigation_style menentukan apakah panel memakai topNavigation() atau sidebarCollapsibleOnDesktop(true) (app/Http/Middleware/FilamentUserSettings.php:40-576). Kedua pembacaan dibungkus try/catch dengan nilai cadangan blue dan sidebar, sehingga kegagalan baca tidak memutus request.
⚠️ Middleware ini berjalan untuk setiap request panel setelah login, bukan sekali saat sesi dibuat. Setiap request membaca tabel
settingsdua kali.
Alur satu request di panel admin
sequenceDiagram
participant B as Browser
participant R as routes/web.php
participant P as AdminPanelProvider
participant M as FilamentUserSettings
participant S as Setting
participant F as Filament Resource
participant Pol as TicketPolicy
B->>R: GET /admin/...
R->>P: panel admin id admin path admin
P->>M: middleware FilamentUserSettings
M->>S: getUserValue filament_primary_color
S-->>M: nama warna atau cadangan blue
M->>P: register FilamentColor primary
M-->>P: next request
P->>F: render halaman atau resource
F->>Pol: can view_ticket
Pol-->>F: permission dan hasAccessToTicket
F-->>B: HTML response
%% fig: Alur request panel admin
%% desc: Request panel admin melewati FilamentUserSettings yang membaca Setting, lalu otorisasi resource diperiksa TicketPolicy sebelum response dikirim.Langkah otorisasi tidak berhenti pada string permission. TicketPolicy memisahkan pemeriksaan izin dari pemeriksaan keterjangkauan data: view mengembalikan $authUser->can('view_ticket') && $this->hasAccessToTicket($authUser, $ticket) (app/Policies/TicketPolicy.php:46-497), sedangkan hasAccessToTicket meloloskan super_admin, pembuat ticket, assignee, dan anggota project pemilik ticket (app/Policies/TicketPolicy.php:22-398). Pola yang sama dipakai update, delete, restore, dan forceDelete. Rincian peran dan permission ada di Kontrol Akses dan Peran.
Portal eksternal klien
Portal eksternal tidak memakai panel Filament. Dua route Livewire menanganinya, yaitu /external/{token} dan /external/{token}/dashboard (routes/web.php:17-209). ExternalLogin memvalidasi $token terhadap kolom access_token yang is_active bernilai true, dan abort(404, 'External access not found') bila tidak ada (app/Livewire/ExternalLogin.php:22-3710). Setelah password cocok, komponen menyimpan external_project_id, external_authenticated, external_authenticated_at_<token>, dan external_authenticated_<token> ke session (app/Livewire/ExternalLogin.php:44-8111).
ExternalDashboard memeriksa session itu di mount, dan menolak sesi yang lebih tua dari SESSION_LIFETIME_MINUTES = 720 menit sebelum mengalihkan kembali ke route login (app/Livewire/ExternalDashboard.php:17-2212, app/Livewire/ExternalDashboard.php:84-11013). Token dan password dibuat oleh ExternalAccess::generateForProject dengan Str::random(32) untuk token dan Str::random(8) untuk password (app/Models/ExternalAccess.php:36-4714); kolom password memakai cast encrypted (app/Models/ExternalAccess.php:25-2915).
⚠️ Kedua route eksternal tidak memuat middleware autentikasi apa pun. Seluruh proteksi bergantung pada
mountkomponen dan kunci session, dan login dibatasiRateLimiterlima percobaan per 300 detik per token dan IP (app/Livewire/ExternalLogin.php:44-8111). Detail perilaku portal ada di Portal Eksternal Klien.
Endpoint yang dipetakan di routes/web.php
| Method | Path | Handler | Jenis |
|---|---|---|---|
| GET | / | closure view('welcome') | halaman statis |
| GET | auth/google | GoogleController::redirectToGoogle | autentikasi |
| GET | auth/google/callback | GoogleController::handleGoogleCallback | autentikasi |
| GET | external/{token} | ExternalLogin | Livewire |
| GET | external/{token}/dashboard | ExternalDashboard | Livewire |
Route halaman depan dan Google berada di bagian atas berkas (routes/web.php:8-1416), sedangkan pasangan route eksternal dibungkus Route::prefix('external')->name('external.') (routes/web.php:17-209). Panel admin tidak muncul di berkas ini karena AdminPanelProvider mendaftarkan path admin sendiri (app/Providers/Filament/AdminPanelProvider.php:25-433).
Layer pendukung
AppServiceProvider mengisi dua peran yang tidak ditangani provider lain. Pertama, komponen Livewire edit-comment-modal diikat manual ke EditCommentModal::class. Kedua, FilamentShield::buildPermissionKeyUsing mengganti cara permission key dibentuk untuk tiga jenis entitas: Resource, Page (page_ + basename), dan Widget (widget_ + basename) (app/Providers/AppServiceProvider.php:27-5417). Bentuk konkret permission key dibahas di Kontrol Akses dan Peran.
EventServiceProvider memetakan dua event secara eksplisit: ProjectMemberAttached ke SendProjectAssignmentNotification, dan ProjectMemberDetached ke SendProjectRemovalNotification (app/Providers/EventServiceProvider.php:18-2518). Karena shouldDiscoverEvents() mengembalikan false, tidak ada listener lain yang ditemukan otomatis (app/Providers/EventServiceProvider.php:38-4119).
NotificationService adalah satu-satunya kelas di app/Services. notifyCommentAdded membuat baris Notification bertipe comment_added untuk setiap pengguna sasaran, dengan payload ticket_id, comment_id, commenter_id, dan commenter_name (app/Services/NotificationService.php:18-3920). Daftar sasaran dirakit getUsersToNotifyForComment dari tiga sumber, yaitu pembuat ticket, assignee ticket selain komentator, dan komentator lain pada ticket yang sama, lalu dirapikan dengan unique('id') (app/Services/NotificationService.php:64-8421).
| Komponen | Peran dalam arsitektur | Rujukan |
|---|---|---|
AdminPanelProvider | mendaftarkan panel admin, middleware, plugin, dan tema | app/Providers/Filament/AdminPanelProvider.php:25-433 |
FilamentUserSettings | menerapkan warna primer dan gaya navigasi per pengguna | app/Http/Middleware/FilamentUserSettings.php:15-295 |
AppServiceProvider | binding komponen Livewire dan format permission key | app/Providers/AppServiceProvider.php:27-5417 |
EventServiceProvider | pemetaan event ke listener, tanpa auto-discovery | app/Providers/EventServiceProvider.php:18-2518 |
NotificationService | pembuatan notifikasi dari komentar ticket | app/Services/NotificationService.php:18-3920 |
TicketPolicy | izin aksi yang digabung dengan cakupan data | app/Policies/TicketPolicy.php:22-398 |
Model domain, relasi antar tabel, dan kolomnya dijelaskan di Model Data dan Migration. Urutan pemasangan provider, panel, dan container ada di Instalasi, Konfigurasi, dan Docker.