8
Menu dan Konfigurasi Aplikasi
Badaso mengelola dua hal dari panel yang sama: struktur navigasi (menus dan menu_items) dan nilai konfigurasi situs (configurations). Keduanya disimpan di tabel berprefiks badaso_ dan dibaca oleh BadasoMenuController serta BadasoConfigurationsController. Pembatasan akses per permission yang dipakai menu item dijelaskan di Autentikasi dan Otorisasi; daftar permission-nya sendiri dibahas di Manajemen User, Role, dan Permission.
Tabel berprefiks dinamis
Ketiga model tidak menuliskan nama tabel secara literal. Konstruktor masing-masing membaca config('badaso.database.prefix') lalu menempelkannya ke nama tabel, sehingga prefiks yang dipilih saat instalasi ikut berlaku pada menu, menu item, dan konfigurasi (src/Models/Menu.php:15-251). Pola yang sama dipakai MenuItem untuk tabel menu_items (src/Models/MenuItem.php:18-362) dan Configuration untuk tabel configurations (src/Models/Configuration.php:18-343). Kolom can_delete pada Configuration dibaca lewat accessor getCanDeleteAttribute yang mengembalikan boolean, bukan angka 0/1 (src/Models/Configuration.php:45-484).
Relasi data
menus adalah induk langsung menu_items lewat kolom menu_id. Di dalam menu_items sendiri ada relasi diri lewat parent_id, sehingga satu item bisa menjadi anak item lain dan membentuk pohon dua tingkat atau lebih. configurations berdiri sendiri: tidak ada foreign key ke menu, hanya kolom group dan order untuk mengelompokkan tampilan di panel.
erDiagram
menus ||--o{ menu_items : "menu_id"
menu_items ||--o{ menu_items : "parent_id"
menus {
bigint id PK
varchar255 key UK
varchar255 display_name
varchar255 icon
integer order
boolean is_show_header
boolean is_expand
}
menu_items {
bigint id PK
bigint menu_id FK
varchar255 title
varchar255 url
varchar255 target
varchar255 icon_class
varchar255 color
integer parent_id FK
integer order
boolean is_expand
text permissions
}
configurations {
bigint id PK
varchar255 key UK
varchar255 display_name
text value
text details
varchar255 type
integer order
varchar255 group
boolean can_delete
}
%% fig: Relasi menus, menu_items, dan configurations
%% desc: menus memiliki banyak menu_items lewat menu_id, menu_items membentuk pohon lewat parent_id, sedangkan configurations berdiri tanpa foreign key.Bentuk kolomnya ditentukan oleh migrasi, bukan oleh model.
| Tabel | Kolom inti | Migrasi pembentuk |
|---|---|---|
menus | key unik, display_name, icon | src/Migrations/2020_11_18_015020_create_menus.php:17-235 |
menus | is_show_header, is_expand, order | src/Migrations/2021_10_21_054710_add_is_show_header_and_expand_in_menus_table.php:16-266 |
menus | order dihapus lalu dibuat ulang sebagai integer nullable | src/Migrations/2022_03_08_090850_change_order_integer_data_type_in_menus_table.php:16-257 |
menu_items | menu_id, title, url, target default _self, parent_id, order, permissions | src/Migrations/2020_11_18_015029_create_menu_items.php:17-298 |
menu_items | is_expand default true | src/Migrations/2021_11_25_100130_add_is_expand_in_badaso_menu_items_table.php:16-209 |
configurations | key unik, value, details, type, order default 1, group, can_delete default false | src/Migrations/2020_11_18_014827_create_configurations.php:17-2810 |
⚠️ Migrasi
2021_10_21_054710membuat kolomorderpadamenussebagaiboolean, lalu migrasi2022_03_08_090850menghapus kolom itu dan menggantinya denganintegernullable (src/Migrations/2021_10_21_054710_add_is_show_header_and_expand_in_menus_table.php:16-266). Kedua migrasi itu tidak boleh dijalankan dengan urutan berbeda.
Siklus permintaan menu item
Panel admin meminta menu item berdasarkan menu_key. Controller memvalidasi bahwa key itu ada di tabel menus, mengambil menunya, lalu melakukan join ke menu_items dan hanya mengambil baris dengan parent_id null (src/Controllers/BadasoMenuController.php:146-17711). Setiap item yang lolos disaring dengan AuthenticatedUser::isAllowedTo, lalu getChildMenuItems menambahkan anak-anaknya secara rekursif (src/Controllers/BadasoMenuController.php:255-27612).
graph LR
panel["Admin panel"] --> route["GET menus/item-by-key"]
route --> ctrl["browseMenuItemByKey"]
ctrl --> menus[("badaso_menus")]
ctrl --> items[("badaso_menu_items")]
ctrl --> allow["AuthenticatedUser::isAllowedTo"]
allow --> child["getChildMenuItems"]
child --> items
child --> resp["ApiResponse::success"]
style menus fill:#336791,color:#fff
style items fill:#336791,color:#fff
style ctrl fill:#f59e0b,color:#000
style child fill:#8b5cf6,color:#fff
style resp fill:#2d7d46,color:#fff
%% fig: Alur browseMenuItemByKey
%% desc: Controller membaca menus dan menu_items, menyaring tiap item dengan isAllowedTo, lalu menyusun anak item secara rekursif sebelum mengembalikan response.Penyaringan menu item berdasarkan permission
isAllowedTo mengembalikan true bila permissions item bernilai null, dan untuk item yang punya daftar permission ia membandingkannya dengan permission user yang sedang login (src/Helpers/AuthenticatedUser.php:44-6213). Menu item tanpa permissions karena itu selalu terlihat oleh setiap user yang bisa membuka panel.
Permission sebuah item disimpan sebagai satu string berkoma. setMenuItemPermissions memetakan id permission menjadi key lewat Permission::whereIn('id', $permission_ids), menyimpannya dengan implode(',', $permissions), dan menulis null bila daftar kosong (src/Controllers/BadasoMenuController.php:625-65214). getMenuItemPermissions melakukan kebalikannya: setiap Permission diberi tanda selected bernilai 1 atau 0 (src/Controllers/BadasoMenuController.php:591-62315).
Urutan, induk, dan status buka
addMenuItem menaruh item baru di urutan terakhir lewat highestOrderMenuItem(), yang mengambil order terbesar pada menu_id dan parent_id yang sama lalu menambahinya satu (src/Models/MenuItem.php:38-5716). Pemanggilan itu terjadi di dalam addMenuItem (src/Controllers/BadasoMenuController.php:310-35217).
editMenuItemOrder menggeser tetangga: bila urutan baru lebih besar, item di antaranya diturunkan satu; bila lebih kecil, item di antaranya dinaikkan satu; setelah itu order item yang dipindah disimpan (src/Controllers/BadasoMenuController.php:430-49418).
editMenuItemsOrder menerima pohon menu_items dan menyerahkannya ke updateMenuItems, yang menulis order = index + 1 beserta parent_id untuk setiap tingkat, lalu turun ke children (src/Controllers/BadasoMenuController.php:522-53319).
menuOptions menangani tiga hal sekaligus berdasarkan isi request: pengurutan ulang menus lewat array order, toggle is_expand untuk menu maupun menu item (dibedakan oleh type), dan toggle is_show_header (src/Controllers/BadasoMenuController.php:30-8720). hasChildren() sendiri hanya menghitung baris dengan parent_id sama dengan id item, dan hasilnya menentukan apakah penelusuran anak perlu dilanjutkan (src/Models/MenuItem.php:59-6521).
Konfigurasi: tipe, details, dan can_delete
browse mengembalikan seluruh konfigurasi terurut kolom order (src/Controllers/BadasoConfigurationsController.php:21-2822), sedangkan fetch memfilter berdasarkan key atau group (src/Controllers/BadasoConfigurationsController.php:46-6923). applyable mengubah kumpulan baris menjadi peta key => value (src/Controllers/BadasoConfigurationsController.php:104-11924).
add menurunkan nama properti dengan Str::snake lalu hanya mengisi kolom yang ada di $configuration->getFillable(). Untuk type checkbox, radio, select, atau select_multiple, details wajib berupa JSON valid; bila tidak, validasi gagal dengan pesan The details must be a valid JSON string. (src/Controllers/BadasoConfigurationsController.php:196-24625).
Saat banyak baris diubah sekaligus, editMultiple menyimpannya dalam satu transaksi lalu memanggil ConfigurationRedis::save(Configuration::all()) sebelum commit (src/Controllers/BadasoConfigurationsController.php:152-19426).
graph TD
req["PUT configurations/edit-multiple"] --> valid["Validator::make per baris"]
valid --> loop["foreach request->configurations"]
loop --> save["Configuration::save"]
save --> redis["ConfigurationRedis::save"]
redis --> db[("badaso_configurations")]
save --> db
style req fill:#f59e0b,color:#000
style redis fill:#8b5cf6,color:#fff
style save fill:#2d7d46,color:#fff
style db fill:#336791,color:#fff
%% fig: Alur editMultiple konfigurasi
%% desc: Setiap baris divalidasi dan disimpan dalam satu transaksi, lalu seluruh konfigurasi ditulis ke cache Redis sebelum commit.delete menolak menghapus konfigurasi yang can_delete bernilai false dengan melempar SingleException('Cannot delete this config') (src/Controllers/BadasoConfigurationsController.php:248-27227).
Endpoint
| Method | Path | Handler | Permission |
|---|---|---|---|
| GET | menus/ | browseMenu | browse_menus |
| GET | menus/read | readMenu | read_menus |
| PUT | menus/edit | editMenu | edit_menus |
| POST | menus/add | addMenu | add_menus |
| DELETE | menus/delete | deleteMenu | delete_menus |
| PUT | menus/arrange-items | editMenuItemsOrder | edit_menus |
| GET | menus/item | browseMenuItem | browse_menu_items |
| GET | menus/item/read | readMenuItem | read_menu_items |
| PUT | menus/item/edit | editMenuItem | edit_menu_items |
| PUT | menus/item/edit-order | editMenuItemOrder | edit_menu_items |
| POST | menus/item/add | addMenuItem | add_menu_items |
| DELETE | menus/item/delete | deleteMenuItem | delete_menu_items |
| GET | menus/item/permissions | getMenuItemPermissions | edit_menu_items |
| PUT | menus/item/permissions | setMenuItemPermissions | edit_menu_items |
| GET | menus/item-by-key | browseMenuItemByKey | tanpa middleware permission |
| GET | menus/item-by-keys | browseMenuItemByKeys | tanpa middleware permission |
| PUT | menus/menu-options | menuOptions | tanpa middleware permission |
| GET | configurations/ | browse | browse_configurations |
| GET | configurations/read | read | read_configurations |
| GET | configurations/fetch | fetch | tanpa middleware permission |
| GET | configurations/fetch-multiple | fetchMultiple | tanpa middleware permission |
| PUT | configurations/edit | edit | edit_configurations |
| PUT | configurations/edit-multiple | editMultiple | edit_configurations |
| POST | configurations/add | add | add_configurations |
| DELETE | configurations/delete | delete | delete_configurations |
Semua route di atas berada di dalam grup middleware yang sama dan memakai config('badaso.middleware.check_permission') sebagai nama middleware pemeriksa permission (src/Routes/api.php:58-9228).
⚠️
GET configurations/applyabledanGET configurations/maintenancedidaftarkan sebelum routeconfigurations/yang memakai middleware permission, sehingga keduanya dapat dipanggil tanpa permissionbrowse_configurations(src/Routes/api.php:58-6129).
Seeder pembentuk nilai awal
MenusSeeder membuat dua menu dengan key general dan core. Bila menu dengan key tersebut sudah ada, baris di-update, bukan dibuat ulang (src/Seeder/Configurations/MenusSeeder.php:22-4230).
FixedMenuItemSeeder mencari kedua menu itu dengan firstOrFail() (src/Seeder/Configurations/FixedMenuItemSeeder.php:23-2431), lalu menyusun daftar $menu_items yang mencakup 14 entri untuk menu core dan general (src/Seeder/Configurations/FixedMenuItemSeeder.php:25-19932). Pencocokan untuk update memakai pasangan menu_id dan url (src/Seeder/Configurations/FixedMenuItemSeeder.php:201-21233). Anak item ditandai dengan parent_id berupa angka, misalnya Files dan Images memakai parent_id 11 agar menempel ke Media Manager (src/Seeder/Configurations/FixedMenuItemSeeder.php:161-18434).
ConfigurationsSeeder menyimpan 13 baris konfigurasi dengan group adminPanel, dan seluruhnya memakai can_delete bernilai 0 (src/Seeder/Configurations/ConfigurationsSeeder.php:22-15335). Sebelum membuat baris, seeder menghapus baris lama yang key-nya sama (src/Seeder/Configurations/ConfigurationsSeeder.php:155-15836).
⚠️ Karena
Configuration::where('key', $value['key'])->delete()dijalankan lebih dulu, menjalankan ulang seeder mengembalikan nilai konfigurasi ke default dan membuang perubahan yang dibuat admin (src/Seeder/Configurations/ConfigurationsSeeder.php:155-15836).