9
Media, Notifikasi, dan Activity Log
Halaman ini membahas tiga jalur pendukung panel Badaso: penyimpanan berkas, notifikasi Firebase Cloud Messaging (FCM), dan pembacaan activity log. Ketiganya bertemu di alur CRUD milik BadasoBaseController, yang menulis activity log lalu memicu notifikasi setiap kali satu record berubah. Pengaturan menu dan halaman panelnya dibahas di Menu dan Konfigurasi Aplikasi dan Frontend Admin Panel.
Endpoint berkas
Route berkas dikumpulkan pada grup file src/Routes/api.php:47-561. Hanya upload yang memakai middleware permission upload_file; route lain di grup ini terbuka bagi siapa pun yang lolos middleware autentikasi induk.
| Route | Handler | Permission |
|---|---|---|
GET /file/view | BadasoFileController@viewFile | — |
GET /file/download | BadasoFileController@downloadFile | — |
POST /file/upload | BadasoFileController@uploadFile | upload_file |
DELETE /file/delete | BadasoFileController@deleteFile | — |
GET /file/browse/lfm | browseFileUsingLfm | — |
POST /file/upload/lfm | uploadFileUsingLfm | — |
GET /file/delete/lfm | deleteFileUsingLfm | — |
GET /file/mimetypes | availableMimetype | — |
Empat method pertama hanya membaca satu kunci dari request lalu menyerahkan pekerjaan ke trait FileHandler src/Controllers/BadasoFileController.php:13-392. Empat method terakhir tidak menyentuh FileHandler sama sekali. Ketiganya membungkus controller unisharp/laravel-filemanager (ItemsController::getItems, UploadController::upload, DeleteController::getDelete) dan membungkus hasilnya dengan ApiResponse, sedangkan availableMimetype mengembalikan config('lfm.folder_categories') src/Controllers/BadasoFileController.php:47-783. Pada uploadFileUsingLfm, keberadaan kunci error di objek hasil memindahkan response ke ApiResponse::failed.
Menulis dan membaca berkas
handleUploadFiles menangani dua bentuk masukan: string path yang sudah jadi, dan array dengan kunci base64. Untuk bentuk base64, helper membuat UUID versi 4, menormalkan spasi pada name menjadi garis bawah, lalu menyusun path uploads/<slug data type>/<custom_path>/<uuid>-<name> dan menyimpannya lewat Storage::disk(config('badaso.storage.disk', 'public'))->put src/Traits/FileHandler.php:11-424. Sisanya tipis: handleDeleteFile memanggil delete() dan handleDownloadFile memanggil download() pada disk yang sama src/Traits/FileHandler.php:44-525.
Pembacaan berkas lebih berlapis. handleViewFile meminta handlePathFile lebih dulu; disk selain public dan local mengembalikan Storage::disk($disk)->url($file), sedangkan dua disk itu mengembalikan ->path($file). Hasilnya dipakai untuk readfile() dengan header Content-type dari mime_content_type atau dari get_headers() bila path berupa URL. Bila terjadi Exception, jalur gagal mengalihkan response ke public_path('badaso-images/badaso.png') src/Traits/FileHandler.php:54-976.
Dari unggahan ke URL publik
sequenceDiagram
autonumber
participant FE as "Admin panel"
participant C as "BadasoFileController"
participant FH as "FileHandler"
participant ST as "Storage disk"
participant AR as "ApiResponse"
participant HF as "HandleFile"
FE->>C: POST /file/upload dengan files
C->>FH: handleUploadFiles(files)
FH->>FH: Uuid::generate(4) dan base64_decode
FH->>ST: put uploads/slug/uuid-nama
FH-->>C: daftar path relatif
C->>AR: ApiResponse::success(path)
AR-->>FE: JSON dengan path relatif
FE->>C: GET /file/... dengan file=path
C->>AR: ApiResponse::success(data)
AR->>HF: HandleFile::handle(response)
HF->>ST: Storage::url(path)
HF-->>AR: path menjadi URL publik
AR-->>FE: JSON dengan URL publik%% fig: Siklus unggah berkas sampai URL publik %% desc: Unggahan menyimpan berkas dan mengembalikan path relatif, sedangkan konversi ke URL publik baru terjadi saat response GET melewati HandleFile.
HandleFile::handle menelusuri array dan objek secara rekursif, lalu handleUrl memutuskan tiga hal: string yang memuat http:// atau https:// dibiarkan apa adanya, string berekstensi gambar (jpg, jpeg, gif, svg, ico, tif, tiff, webp, heif, png, bmp) diubah dengan Storage::url, dan string yang memuat prefiks files/ atau photos/ ditukar menjadi Storage::url('/') diikuti nama folder tersebut src/Helpers/HandleFile.php:24-517. Arah sebaliknya disediakan handleUrl tidak; normalize dan removeBaseUrl yang menghapus Storage::url('/') dari nilai masuk src/Helpers/HandleFile.php:53-748.
⚠️ Konversi hanya berjalan pada response
GET.ApiResponse::sendmemeriksa$request->method() === 'GET'sebelum memanggilHandleFile::handle, sehingga responsePOSTatauPUTmemuat path relatif apa adanya src/Helpers/ApiResponse.php:14-239. Di sisi masuk, middleware memanggil$request->merge(HandleFile::normalize($request->all()))src/Middleware/ApiRequest.php:7010, jadi klien boleh mengirim URL penuh dan menyimpannya sebagai path.
Batas unggahan dari konfigurasi lfm
| Kunci | Nilai |
|---|---|
folder_categories.file.folder_name | files, startup_view list, max_size 50000 KB, valid_mime application/pdf dan text/plain |
folder_categories.image.folder_name | photos, startup_view grid, max_size 50000 KB, valid_mime image/jpeg, image/jpg, image/pjpeg, image/png, image/gif |
disk | env('FILESYSTEM_DRIVER', 'public') |
rename_duplicates / over_write_on_duplicate | true / false |
should_create_thumbnails, thumb_img_width, thumb_img_height | true, 200, 200 |
prefix_route | /filemanager |
Nilai-nilai itu berasal dari src/Config/lfm.php:48-7011 dan src/Config/lfm.php:88-11112. Ukuran maksimum dan validasi mime baru berlaku bila should_validate_size dan should_validate_mime diaktifkan; pada berkas ini should_validate_size bernilai false dan should_validate_mime bernilai true src/Config/lfm.php:104-10613. Untuk memori, php_ini_overrides menaikkan memory_limit ke 256M, dan middleware grup filemanager hanya web plus BadasoAuthenticateIframe:lfm src/Config/lfm.php:168-18414.
Notifikasi FCM
Menyimpan token perangkat
saveTokenMessage memvalidasi token_get_message wajib ada, mengambil user dari Auth::guard(config('badaso.authenticate.guard')), lalu memperbarui baris FirebaseCloudMessages milik user itu atau membuat baris baru bila belum ada. Response sukses hanya mengembalikan kembali token tersebut src/Controllers/BadasoFCMController.php:12-4215. Route-nya PUT /firebase/cloud_messages/save-token-messages di dalam grup yang memakai config('badaso.middleware.authenticate') src/Routes/api.php:228-23716.
Mengirim notifikasi
FCMNotification menyimpan konstanta $FIREBASE_URL_API = 'https://fcm.googleapis.com/fcm/send' serta empat nama event: onCreate, onUpdate, onRead, dan onDelete. Konstruktornya membaca env('MIX_FIREBASE_SERVER_KEY', ''), config('firebase.priority'), dan config('firebase.tell_role_names') src/Helpers/Firebase/FCMNotification.php:13-3417. send menyusun payload registration_ids, notification.title, notification.body, priority, dan data, lalu memanggil Client::post dengan header Authorization: Bearer <server key>; pengiriman dilewati bila count($ids) nol src/Helpers/Firebase/FCMNotification.php:39-6418.
notificationEvent melakukan empat hal berurutan: mencari DataType dengan name sama dengan nama tabel, mengambil entri ber-event yang cocok dari kolom notification, menimpa title dan body bila pesan kustom pada entri itu tidak kosong, lalu mengumpulkan penerima lewat join firebase_cloud_messages dan user_roles–roles dengan whereIn('roles.name', $this->tell_role_names) sambil mengecualikan user yang sedang login. Setiap penerima mendapat satu baris Notification (type notification, is_read false) sebelum send dipanggil dengan data['fcm_message_id'] src/Helpers/Firebase/FCMNotification.php:69-12419. Dua nilai konfigurasi yang dipakai adalah priority 'high' dan tell_role_names ['administrator', 'customer'] src/Config/firebase.php:3-820.
graph LR
A["BadasoBaseController add/edit/delete"] --> B["FCMNotification::notification"]
B --> C{"count($data) > 0"}
C -->|ya| D["notificationEvent"]
C -->|tidak| X["berhenti tanpa kirim"]
D --> E[("data_types.notification")]
D --> F[("firebase_cloud_messages")]
D --> G[("user_roles dan roles")]
D --> H["Notification::create"]
H --> ST[("notifications")]
D --> I["send"]
I --> J["POST fcm.googleapis.com/fcm/send"]
style C fill:#f59e0b,color:#000
style E fill:#336791,color:#fff
style ST fill:#336791,color:#fff
style F fill:#2d7d46,color:#fff
style G fill:#8b5cf6,color:#fff
style J fill:#2d7d46,color:#fff
style X fill:#dc2626,color:#fff%% fig: Jalur pengiriman notifikasi FCM %% desc: notificationEvent membaca konfigurasi event per tabel, menyusun daftar penerima, menulis baris notifications, lalu memanggil endpoint FCM.
⚠️
notificationhanya memanggilnotificationEventbilacount($data) > 0src/Helpers/Firebase/FCMNotification.php:159-16221. Pemanggil diBadasoBaseControllermengirim dua argumen saja, sehingga$datatetap array kosong dan pengiriman berhenti di gerbang itu src/Controllers/BadasoBaseController.php:73-7622. Pemanggil perlu menambahkan elemendataagar notifikasi benar-benar terkirim.
Kotak masuk di panel
Tiga endpoint melayani pembacaan pesan: GET /firebase/messages, PUT /firebase/messages/{id}, dan GET /firebase/messages/count-unread src/Routes/api.php:228-23716. getMessages mengambil Notification milik receiver_user_id user aktif dengan urutan created_at menurun, lalu memanggil relasi sender_users pada setiap baris. readMessage($id) mengubah is_read menjadi true. getCountUnreadMessage menghitung baris dengan where('is_read', true) src/Controllers/BadasoNotificationsController.php:11-6023.
stateDiagram-v2
[*] --> unread: notificationEvent menulis is_read false
unread --> read: readMessage(id) mengubah is_read true
read --> [*]
note right of read
getCountUnreadMessage menghitung
baris dengan is_read = true
end note
style unread fill:#f59e0b,color:#000
style read fill:#2d7d46,color:#fff%% fig: Siklus hidup status is_read %% desc: Satu pesan lahir belum terbaca, berubah terbaca lewat readMessage, dan penghitung unread justru membaca state terbaca.
⚠️ Nama
getCountUnreadMessagetidak sesuai isinya: query-nya memakaiwhere('is_read', true), sehingga yang dihitung adalah pesan yang sudah dibaca src/Controllers/BadasoNotificationsController.php:5224. Kolomis_readsendiri didefinisikanbooleandengan default0pada tabelf_c_m_messagessrc/Migrations/2021_04_28_004319_create_f_c_m_messages_table.php:16-2825, danNotificationmengisi tabel${prefix}notificationsdenganfillableyang memuatis_read,sender_user_id, danreceiver_user_idsrc/Models/Notification.php:22-3426.
Activity log
Penulisan log memakai helper activity() dari spatie/laravel-activitylog. Pada edit, setelah DB::commit(), kode mencatat activity($data_type->display_name_singular)->causedBy($user_auth)->withProperties(['old' => ..., 'attributes' => ...])->log('... has been updated') src/Controllers/BadasoBaseController.php:116-12627. Pembacaan disediakan dua endpoint, GET /activitylogs dan GET /activitylogs/read, yang masing-masing dijaga permission browse_activitylogs dan read_activitylogs src/Routes/api.php:21-2428.
| Endpoint | Parameter | Perilaku |
|---|---|---|
GET /activitylogs | limit (default 10), filter, order_field, order_direction | filter dibungkus %...% dan dicocokkan LIKE ke log_name, description, subject_id, subject_type, causer_id, causer_type, properties; urutan default created_at desc; hasil dipaginasi lalu tiap baris diberi causer_name dari User::find |
GET /activitylogs/read | id wajib, exists:activity_log,id | mengembalikan activitylog, subject, causer, dan properties |
Rincian itu ada di src/Controllers/BadasoActivityLogController.php:14-5729 dan src/Controllers/BadasoActivityLogController.php:59-7730. Bila order_field dikirim, nama kolom dan arahnya diubah dulu dengan Str::snake sebelum orderBy.
⚠️ Rantai filter memakai
orWheretanpa pembungkus grup, sehinggalimit,orderBy, dan kondisi lain dapat kehilangan efek penyaringan begitu satu klausaLIKEcocok src/Controllers/BadasoActivityLogController.php:22-2931. Tabelactivity_logjuga didaftarkan sebagai tabel tersembunyi dibadaso-hidden-tables.php, jadi ia tidak muncul sebagai entitas CRUD.
Backup dan log viewer
badaso:backup membaca dua variabel lingkungan. BACKUP_TARGET menentukan cabang: all memanggil backup:run polos, database menambahkan --only-db, dan files menambahkan --only-files. BACKUP_DISK wajib terisi lebih dulu, karena tanpa itu perintah berhenti dengan pesan No disk specified. Set to env some of [s3, google, dropbox]. Target kosong menghasilkan No target to backup., dan nilai di luar tiga cabang menghasilkan Invalid Backup Target. Set to env one of [all, database, files] sebelum mencetak Backup done src/Commands/BackupCommand.php:39-8332.
⚠️ Cabang
allmenjalankanbackup:runtanpa pembatas, dan konfigurasi sumbernya menyertakan seluruhbase_path()kecualivendorsertanode_modules. Hasilnya zip berisi seluruh proyek, bukan hanya basis data src/Config/backup.php:11-8933. Disk tujuan diambil dariexplode(',', env('BACKUP_DISK', '')), basis data yang didukung hanyamysql, dan berkas sementara diletakkan distorage_path('app/backup-temp').
Log viewer tidak menyimpan apa pun sendiri; ia membaca berkas di storage_path('logs') dengan pola prefiks laravel- dan ekstensi .log src/Config/log-viewer.php:11-5734. Route-nya aktif ('enabled' => true) dengan prefiks env('VITE_LOG_VIEWER_ROUTE', 'log-viewer'), middleware BadasoAuthenticateIframe:log-viewer, dan 30 entri per halaman. Variabel yang sama ditulis BadasoSetup ke .env bersama BACKUP_TARGET dan BACKUP_DISK saat instalasi, sehingga nilainya sudah tersedia pada proyek baru.