Lompat ke konten
dokumentasi.id
Commit b472665
v1
Star1,3 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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-56. Hanya upload yang memakai middleware permission upload_file; route lain di grup ini terbuka bagi siapa pun yang lolos middleware autentikasi induk.

RouteHandlerPermission
GET /file/viewBadasoFileController@viewFile—
GET /file/downloadBadasoFileController@downloadFile—
POST /file/uploadBadasoFileController@uploadFileupload_file
DELETE /file/deleteBadasoFileController@deleteFile—
GET /file/browse/lfmbrowseFileUsingLfm—
POST /file/upload/lfmuploadFileUsingLfm—
GET /file/delete/lfmdeleteFileUsingLfm—
GET /file/mimetypesavailableMimetype—

Empat method pertama hanya membaca satu kunci dari request lalu menyerahkan pekerjaan ke trait FileHandler src/Controllers/BadasoFileController.php:13-39. 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-78. Pada uploadFileUsingLfm, keberadaan kunci error di objek hasil memindahkan response ke ApiResponse::failed.

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-42. Sisanya tipis: handleDeleteFile memanggil delete() dan handleDownloadFile memanggil download() pada disk yang sama src/Traits/FileHandler.php:44-52.

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-97.

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-51. Arah sebaliknya disediakan handleUrl tidak; normalize dan removeBaseUrl yang menghapus Storage::url('/') dari nilai masuk src/Helpers/HandleFile.php:53-74.

⚠️ Konversi hanya berjalan pada response GET. ApiResponse::send memeriksa $request->method() === 'GET' sebelum memanggil HandleFile::handle, sehingga response POST atau PUT memuat path relatif apa adanya src/Helpers/ApiResponse.php:14-23. Di sisi masuk, middleware memanggil $request->merge(HandleFile::normalize($request->all())) src/Middleware/ApiRequest.php:70, jadi klien boleh mengirim URL penuh dan menyimpannya sebagai path.

Batas unggahan dari konfigurasi lfm

KunciNilai
folder_categories.file.folder_namefiles, startup_view list, max_size 50000 KB, valid_mime application/pdf dan text/plain
folder_categories.image.folder_namephotos, startup_view grid, max_size 50000 KB, valid_mime image/jpeg, image/jpg, image/pjpeg, image/png, image/gif
diskenv('FILESYSTEM_DRIVER', 'public')
rename_duplicates / over_write_on_duplicatetrue / false
should_create_thumbnails, thumb_img_width, thumb_img_heighttrue, 200, 200
prefix_route/filemanager

Nilai-nilai itu berasal dari src/Config/lfm.php:48-70 dan src/Config/lfm.php:88-111. 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-106. Untuk memori, php_ini_overrides menaikkan memory_limit ke 256M, dan middleware grup filemanager hanya web plus BadasoAuthenticateIframe:lfm src/Config/lfm.php:168-184.

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-42. Route-nya PUT /firebase/cloud_messages/save-token-messages di dalam grup yang memakai config('badaso.middleware.authenticate') src/Routes/api.php:228-237.

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-34. 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-64.

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-124. Dua nilai konfigurasi yang dipakai adalah priority 'high' dan tell_role_names ['administrator', 'customer'] src/Config/firebase.php:3-8.

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.

⚠️ notification hanya memanggil notificationEvent bila count($data) > 0 src/Helpers/Firebase/FCMNotification.php:159-162. Pemanggil di BadasoBaseController mengirim dua argumen saja, sehingga $data tetap array kosong dan pengiriman berhenti di gerbang itu src/Controllers/BadasoBaseController.php:73-76. Pemanggil perlu menambahkan elemen data agar 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-237. 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-60.

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 getCountUnreadMessage tidak sesuai isinya: query-nya memakai where('is_read', true), sehingga yang dihitung adalah pesan yang sudah dibaca src/Controllers/BadasoNotificationsController.php:52. Kolom is_read sendiri didefinisikan boolean dengan default 0 pada tabel f_c_m_messages src/Migrations/2021_04_28_004319_create_f_c_m_messages_table.php:16-28, dan Notification mengisi tabel ${prefix}notifications dengan fillable yang memuat is_read, sender_user_id, dan receiver_user_id src/Models/Notification.php:22-34.

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-126. 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-24.

EndpointParameterPerilaku
GET /activitylogslimit (default 10), filter, order_field, order_directionfilter 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/readid wajib, exists:activity_log,idmengembalikan activitylog, subject, causer, dan properties

Rincian itu ada di src/Controllers/BadasoActivityLogController.php:14-57 dan src/Controllers/BadasoActivityLogController.php:59-77. Bila order_field dikirim, nama kolom dan arahnya diubah dulu dengan Str::snake sebelum orderBy.

⚠️ Rantai filter memakai orWhere tanpa pembungkus grup, sehingga limit, orderBy, dan kondisi lain dapat kehilangan efek penyaringan begitu satu klausa LIKE cocok src/Controllers/BadasoActivityLogController.php:22-29. Tabel activity_log juga didaftarkan sebagai tabel tersembunyi di badaso-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-83.

⚠️ Cabang all menjalankan backup:run tanpa pembatas, dan konfigurasi sumbernya menyertakan seluruh base_path() kecuali vendor serta node_modules. Hasilnya zip berisi seluruh proyek, bukan hanya basis data src/Config/backup.php:11-89. Disk tujuan diambil dari explode(',', env('BACKUP_DISK', '')), basis data yang didukung hanya mysql, dan berkas sementara diletakkan di storage_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-57. 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.