Modul Setup
Ringkasan
Modul Setup adalah wizard pertama-kali yang mengkonfigurasi instalasi Sutomo yang masih fresh. Bertindak sebagai gerbang: sampai semua data yang dibutuhkan tersedia, setiap request akan di-redirect ke /setup. Setelah selesai, aplikasi berjalan normal.
Modul ini mencakup backend (PHP middleware + controllers) dan frontend (Svelte wizard). Ini adalah satu-satunya modul yang bisa mengunci seluruh aplikasi.
Konsep
Status Initial
Instalasi fresh tidak punya data apa pun. Sistem menggunakan tiga level pengecekan di IdentifierAppService:
| Check | Pertanyaan | Dipakai oleh |
|---|---|---|
setup() | Apakah super user ada? | SetupMiddleware |
seeder() | Apakah database sudah di-seed? | MaintenanceMiddleware |
initial() | Apakah konten publik siap? | Maintenance + frontend |
Tier Kelengkapan Setup
isSetup() menggunakan 5 tier. Setiap tier harus lulus sebelum tier berikutnya diperiksa:
| Tier | Persyaratan | Dampak jika gagal |
|---|---|---|
| 1 — Auth | Minimal 1 User (admin) | Tidak bisa login |
| 2 — Seeds | Geographic, gender, ethnicity, localization, religion | Form dropdown kosong |
| 3 — Konten | 1 Sekolah + 1 Artikel | Halaman publik kosong |
| 4 — Branding | Core settings + yayasan_name | Header/footer/SEO tidak lengkap |
| 5 — Kontak | 1 kontak + 1 sosial media | Publik tidak bisa hubungi sekolah |
State Machine
┌──────────┐
│ FRESH │
└────┬─────┘
│ Step 1: POST /api/setup/register
▼
┌─────────────────────┐
│ SUPER USER DIBUAT │
└────────┬────────────┘
│ Step 2: POST /api/setup/seed
▼
┌─────────────────────┐
│ DATABASE DI-SEED │
└────────┬────────────┘
│ Step 3: POST /api/setup/about
▼
┌─────────────────────┐
│ ABOUT DIKONFIG │
└────────┬────────────┘
│ Step 4: POST /api/setup/setting
▼
┌─────────────────────┐
│ SETUP SELESAI │
│ isSetup() → true │
└─────────────────────┘Flowchart
Gerbang Middleware
Penjelasan:
- SetupMiddleware berjalan pertama di setiap request. Cek
IdentifierAppService::isSetup(). - Jika setup belum selesai, user dikirim ke
/setup— apapun URL yang diminta. - Jika setup selesai, request lanjut ke MaintenanceMiddleware.
- MaintenanceMiddleware bypass route
/setup*,/portal*,/api/setup*. - Jika mode maintenance ON, tampilkan halaman
UnderConstruction. - Jika database sudah di-seed (
isSeeder()), user di-redirect ke panel admin/portal. - Hanya jika semua lolos, landing page publik dirender.
Auto-Navigasi
Penjelasan:
- Backend mengembalikan 8 item pengecekan dari
getCheckStatuses(). - Jika
checks[0].status !== 'completed'→ user harus isi Step 1 (Super User). - Jika ada
checks[1..4]yang pending → Step 2 (Seed). - Jika ada
checks[5..7]yang pending → Step 3 (About/Settings). - Jika semua 8 check lolos → wizard langsung ke Step 5 (Done).
SSE Seed Stream
Tipe Event SSE
| Event | Arah | Payload | Saat |
|---|---|---|---|
start | Server → Client | { total: 7 } | Semua seeder dimulai |
progress | Server → Client | { label, current, total, percent } | Setiap seeder mulai |
seederComplete | Server → Client | { label, current, total } | Setiap seeder selesai |
checks | Server → Client | { checks: CheckItem[] } | Setelah setiap seeder |
complete | Server → Client | { message, checks } | Semua seeder selesai |
error | Server → Client | { message } | Seeder gagal |
State Machine SSE
IDLE → CONNECTING → STREAMING → COMPLETE
│
└── ERROR → IDLE (retry)Pemisahan Store
Proses seed menggunakan dua store karena mengelola concern yang berbeda:
seed.stores.ts — Status proses seed:
seeding: boolean— apakah seed sedang berjalan?progress: { label, current, total }— info seeder saat inichecks: CheckItem[]— status sistem terbaruerror: string | null— pesan error
seed-sse.stores.ts — Status koneksi SSE:
connecting: boolean— apakah request HTTP sedang berlangsung?connected: boolean— apakah stream aktif?error: string | null— error koneksi
Kenapa SSE Bukan Polling
Proses seed menjalankan 7 seeder secara berurutan, memakan waktu beberapa detik. Polling akan memaksa frontend bertanya "sudah selesai?" berulang kali (boros dan lambat). SSE memungkinkan server mendorong update progress saat setiap seeder selesai, memberikan umpan balik UI instan.
Algoritma
isSetup() — Pengecekan Master
File: app/Services/Common/IdentifierAppService.php
Single source of truth. Setiap request kena check ini via SetupMiddleware.
public static function isSetup(): bool
{
if (! User::query()->exists()) return false; // Tier 1
if (! Setting::query()->where('module', 'core')->where('group', 'location')->exists()) return false; // Tier 2
if (! Gender::query()->exists() || ! Ethnicity::query()->exists()) return false;
if (! Religion::query()->exists()) return false;
if (! Localization::query()->exists()) return false;
if (! School::query()->exists() || ! SchoolEducationLevel::query()->exists()) return false; // Tier 3
if (! Article::query()->exists()) return false;
if (! Setting::query()->where('module', 'core')->where('group', 'general')->exists()) return false; // Tier 4
if (! Setting::query()->where('module', 'core')->where('key', 'yayasan_name')->exists()) return false;
if (! Setting::query()->where('module', 'contact')->exists()) return false; // Tier 5
if (! Setting::query()->where('module', 'social')->exists()) return false;
return true;
}Logika: Setiap return false adalah gerbang. Item pertama yang hilang langsung di-reject. Check-nya O(n) di worst case tapi biasanya fail fast di Tier 1 atau 2.
seeder() — Kesiapan Database
File: app/Services/Common/IdentifierAppService.php
public static function seeder(): array
{
return [
'geographic' => Setting::query()->where('module', 'core')->where('group', 'location')->exists(),
'gender' => Gender::query()->exists(),
'ethnicity' => Ethnicity::query()->exists(),
'localization' => Localization::query()->exists(),
'religion' => Religion::query()->exists(),
];
}Tujuan: Mengembalikan boolean individual agar frontend tahu seeder mana yang sudah/jalan.
Method isSeeder():
public static function isSeeder(): bool
{
$s = self::seeder();
return $s['geographic'] && $s['gender'] && $s['ethnicity']
&& $s['localization'] && $s['religion'];
}Kenapa ada terpisah dari isSetup(): isSeeder() sengaja kurang strict. Begitu database punya data fundamental (region, gender, agama), user di-redirect ke portal admin meski branding dan kontak belum diatur. Asumsinya admin bisa selesaikan via panel.
initial() — Kesiapan Konten
File: app/Services/Common/IdentifierAppService.php
public static function initial(): array
{
return [
'school' => School::query()->exists() && SchoolEducationLevel::query()->exists(),
'news' => Article::query()->exists(),
'ratings' => Rating::query()->exists(),
'banner' => Banner::query()->exists(),
'announcement' => Announcement::query()->exists(),
'faq' => Faq::query()->exists(),
];
}Tujuan: Kesiapan konten per-fitur. Setiap key bisa siap/tidak siap secara independen.
Hubungan dengan isSetup():
isSetup()cekinitial()['school']daninitial()['news'](wajib).initial()['ratings'],['banner'], dll tidak diwajibkan olehisSetup().
Auto-Navigasi Frontend
File: resources/js/modules/landing/setup/form/shared/services/step-control-actions.ts
Algoritma
export function computeInitialStep(checks: CheckItem[]): number {
if (checks.length === 0) return 1;
if (checks[0]?.status !== 'completed') return 1;
const seedChecks = checks.slice(1, 5);
if (seedChecks.some((c) => c.status !== 'completed')) return 2;
const aboutChecks = checks.slice(5, 8);
if (aboutChecks.some((c) => c.status !== 'completed')) return 3;
return 5;
}Integrasi di Index.svelte
<script lang="ts">
let currentStep = $state(computeInitialStep(initialChecks));
// ↑ dihitung LANGSUNG
// BUKAN di $effect
$effect(() => {
const unsub = stepControl.subscribe((s) => {
currentStep = s.currentStep;
});
return () => unsub();
});
</script>Aturan penting: computeInitialStep() HARUS dipanggil sinkron saat component init. Jangan pakai $state(1) lalu update di $effect — ini menyebabkan flash render dan potensi SSR hydration mismatch.
Mapping: Backend Checks → Wizard Steps
| Index Check | Label Check | Wizard Step | Kondisi Gagal |
|---|---|---|---|
| 0 | Super User Account | 1 - Super User | Belum ada admin |
| 1 | Geographic Data | 2 - Seed | Kota/provinsi belum di-seed |
| 2 | Core Settings | 2 - Seed | Setting aplikasi kosong |
| 3 | Gender, Ethnicity & Religion | 2 - Seed | Dropdown form kosong |
| 4 | Localization | 2 - Seed | Lokalisasi belum tersedia |
| 5 | Schools | 3 - About | Belum ada sekolah |
| 6 | Articles / News | 3 - About | Berita publik kosong |
| 7 | About Us Configurations | 3 - About | Branding belum lengkap |
File Penting
Backend
| File | Peran |
|---|---|
app/Http/Middleware/Setup/SetupMiddleware.php | Gerbang tiap request. Redirect ke /setup jika isSetup() false. Bypass /setup* dan /api/setup*. |
app/Http/Middleware/Setup/MaintenanceMiddleware.php | Gerbang kedua. Tampilkan maintenance atau redirect ke /portal. Bypass /setup*, /portal*, /api/setup*. |
app/Services/Common/IdentifierAppService.php | Check state terpusat. Berisi setup(), seeder(), initial(), isSetup(), isSeeder(), isMaintenance(). |
app/Http/Controllers/Landing/SetupController.php | Sajikan halaman Inertia setup + handle semua API step. |
routes/api/setup.php | Definisi route API untuk semua endpoint setup. |
routes/web/landing/setup.php | Route web untuk halaman Inertia /setup. |
database/migrations/0000_runner_migration.php | Custom migration runner. Baca env MIGRATION_RUNNER_FOLDERS. |
Frontend
| File | Peran |
|---|---|
resources/js/pages/landing/Setup.svelte | Halaman Inertia. Terima checks + setupComplete dari backend. Render wizard atau layar selesai. |
resources/js/modules/landing/setup/form/Index.svelte | Entry point wizard. Hitung initial step, render komponen step yang benar. |
resources/js/modules/landing/setup/form/shared/stores/form-setup-step-control.stores.ts | State navigasi step (next, prev, goToStep, configure). |
resources/js/modules/landing/setup/form/shared/services/step-control-actions.ts | Pure functions: computeInitialStep(), computeStepLabels(). |
resources/js/modules/landing/setup/form/super-user/ | Step 1 — Registrasi super user dengan OTP. |
resources/js/modules/landing/setup/form/seed/ | Step 2 — Database seeding via SSE dengan progress real-time. |
resources/js/modules/landing/setup/form/about/ | Step 3 — Form about, contact, social media. |
resources/js/modules/landing/setup/form/setting/ | Step 4 — Konfigurasi theme, appearance, contact/social. |
resources/js/modules/landing/setup/tutorial/ | Panel tutorial sidebar yang menampilkan status setup. |