Kirim Rekomendasi Wisata & Peninjauan Admin
Masyarakat bisa ikut menyumbang rekomendasi tempat wisata baru yang tersembunyi, yang akan ditinjau terlebih dahulu oleh admin sebelum tampil di situs.
Masyarakat bisa ikut menyumbang rekomendasi tempat wisata baru yang tersembunyi, yang akan ditinjau terlebih dahulu oleh admin sebelum tampil di situs.
Tantangan & Kebutuhan Pengguna
Platform direktori wisata yang mengandalkan kontribusi warga lokal (crowdsourcing) rentan terhadap kiriman spam, duplikasi judul yang memicu bentrok URL slug, serta kebocoran destinasi yang belum diverifikasi ke halaman publik maupun ke dalam konteks AI Trip Planner.
Buka lebar pintu kontribusi bagi komunitas, namun kunci rapat gerbang publikasi melalui state status bertipe kuat dan filter query yang konsisten.
Solusi & Cara Kerja
Pengajuan dari masyarakat diproses melalui `StoreSubmissionRequest` dan `SubmissionService` yang secara otomatis memberi status awal `pending` serta menambahkan sufiks 5 karakter acak pada slug (`Str::slug($title) . '-' . Str::random(5)`). Status destinasi dimodelkan menggunakan PHP 8.3 Backed Enum `DestinationStatus` (`Pending`, `Approved`, `Rejected`) yang di-cast langsung pada model `Destination`. Seluruh controller publik dan AI hanya memanggil `Destination::approved()`, sementara Admin memiliki antarmuka kurasi lengkap dengan pencarian *advance field* dan pencatatan alasan penolakan (`rejection_note`).
Berikut adalah implementasi `DestinationStatus` Enum, metode kurasi di `DestinationService`, dan filter pencarian aman di `Admin\DestinationController`:
Detail & Hal yang Diperhatikan
- Pencegahan tabrakan slug dari pengirim anonim: Dua pengunjung bisa mengirimkan rekomendasi tempat yang sama secara bersamaan. Penambahan sufiks acak di `SubmissionService` mencegah kegagalan constraint `UNIQUE` pada kolom `slug`.
- Pencegahan SQL Column Injection pada fitur Advance Search: Parameter `advance_field` dari query string divalidasi secara ketat menggunakan `in_array($field, $allowedFields, true)` sebelum dipakai di dalam klausa `where($field, ...)`.
- Sinkronisasi state saat re-approval: Ketika destinasi yang sempat ditolak diperbaiki lalu disetujui oleh admin, metode `approve()` otomatis membersihkan `rejection_note` menjadi `null` dan mengisi waktu tayang `published_at`.
Penggunaan `withQueryString()` pada pagination admin memastikan filter status (`pending`/`approved`/`rejected`) dan kata kunci pencarian tetap bertahan saat admin berpindah halaman tabel.