Skip to content

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:

CheckPertanyaanDipakai 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:

TierPersyaratanDampak jika gagal
1 — AuthMinimal 1 User (admin)Tidak bisa login
2 — SeedsGeographic, gender, ethnicity, localization, religionForm dropdown kosong
3 — Konten1 Sekolah + 1 ArtikelHalaman publik kosong
4 — BrandingCore settings + yayasan_nameHeader/footer/SEO tidak lengkap
5 — Kontak1 kontak + 1 sosial mediaPublik 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:

  1. SetupMiddleware berjalan pertama di setiap request. Cek IdentifierAppService::isSetup().
  2. Jika setup belum selesai, user dikirim ke /setup — apapun URL yang diminta.
  3. Jika setup selesai, request lanjut ke MaintenanceMiddleware.
  4. MaintenanceMiddleware bypass route /setup*, /portal*, /api/setup*.
  5. Jika mode maintenance ON, tampilkan halaman UnderConstruction.
  6. Jika database sudah di-seed (isSeeder()), user di-redirect ke panel admin /portal.
  7. Hanya jika semua lolos, landing page publik dirender.

Auto-Navigasi

Penjelasan:

  1. Backend mengembalikan 8 item pengecekan dari getCheckStatuses().
  2. Jika checks[0].status !== 'completed' → user harus isi Step 1 (Super User).
  3. Jika ada checks[1..4] yang pending → Step 2 (Seed).
  4. Jika ada checks[5..7] yang pending → Step 3 (About/Settings).
  5. Jika semua 8 check lolos → wizard langsung ke Step 5 (Done).

SSE Seed Stream

Tipe Event SSE

EventArahPayloadSaat
startServer → Client{ total: 7 }Semua seeder dimulai
progressServer → Client{ label, current, total, percent }Setiap seeder mulai
seederCompleteServer → Client{ label, current, total }Setiap seeder selesai
checksServer → Client{ checks: CheckItem[] }Setelah setiap seeder
completeServer → Client{ message, checks }Semua seeder selesai
errorServer → 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 ini
  • checks: CheckItem[] — status sistem terbaru
  • error: 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.

php
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

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():

php
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

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() cek initial()['school'] dan initial()['news'] (wajib).
  • initial()['ratings'], ['banner'], dll tidak diwajibkan oleh isSetup().

Auto-Navigasi Frontend

File: resources/js/modules/landing/setup/form/shared/services/step-control-actions.ts

Algoritma

typescript
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

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 CheckLabel CheckWizard StepKondisi Gagal
0Super User Account1 - Super UserBelum ada admin
1Geographic Data2 - SeedKota/provinsi belum di-seed
2Core Settings2 - SeedSetting aplikasi kosong
3Gender, Ethnicity & Religion2 - SeedDropdown form kosong
4Localization2 - SeedLokalisasi belum tersedia
5Schools3 - AboutBelum ada sekolah
6Articles / News3 - AboutBerita publik kosong
7About Us Configurations3 - AboutBranding belum lengkap

File Penting

Backend

FilePeran
app/Http/Middleware/Setup/SetupMiddleware.phpGerbang tiap request. Redirect ke /setup jika isSetup() false. Bypass /setup* dan /api/setup*.
app/Http/Middleware/Setup/MaintenanceMiddleware.phpGerbang kedua. Tampilkan maintenance atau redirect ke /portal. Bypass /setup*, /portal*, /api/setup*.
app/Services/Common/IdentifierAppService.phpCheck state terpusat. Berisi setup(), seeder(), initial(), isSetup(), isSeeder(), isMaintenance().
app/Http/Controllers/Landing/SetupController.phpSajikan halaman Inertia setup + handle semua API step.
routes/api/setup.phpDefinisi route API untuk semua endpoint setup.
routes/web/landing/setup.phpRoute web untuk halaman Inertia /setup.
database/migrations/0000_runner_migration.phpCustom migration runner. Baca env MIGRATION_RUNNER_FOLDERS.

Frontend

FilePeran
resources/js/pages/landing/Setup.svelteHalaman Inertia. Terima checks + setupComplete dari backend. Render wizard atau layar selesai.
resources/js/modules/landing/setup/form/Index.svelteEntry point wizard. Hitung initial step, render komponen step yang benar.
resources/js/modules/landing/setup/form/shared/stores/form-setup-step-control.stores.tsState navigasi step (next, prev, goToStep, configure).
resources/js/modules/landing/setup/form/shared/services/step-control-actions.tsPure 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.