Skip to content

Setup Wizard — Algoritma & Arsitektur

Setup Wizard memandu administrator pertama melalui konfigurasi awal. Akses di /setup.

Alur Request (Middleware)

Request ──► SetupMiddleware ──► MaintenanceMiddleware ──► Controller
                  │                      │
     ┌────────────┘                      │
     ▼                                   ▼
  isSetup() == false?           isMaintenance() == true?
     ├─ Ya → redirect /setup       ├─ Ya → Halaman UnderConstruction
     └─ Tidak → continue           └─ Tidak → continue


                                isSeeder() == true?
                                  ├─ Ya → redirect /portal (admin)
                                  └─ Tidak → render landing

Algoritma Utama: IdentifierAppService::isSetup()

Ini pengecekan master yang menentukan apakah setup selesai. File: app/Services/Common/IdentifierAppService.php:

php
public static function isSetup(): bool
{
    if (! $setup['superUser']) return false;           // 1. Super user
    if (! $seeder['geographic']) return false;          // 2. Seeder
    if (! $seeder['gender'] || ! $seeder['ethnicity'] || ! $seeder['religion']) return false;
    if (! $seeder['localization']) return false;
    if (! coreSettingsExist()) return false;            // 3. Core settings
    if (! yayasanNameExists()) return false;            // 4. About
    if (! $initial['school']) return false;             // 5. School
    if (! $initial['news']) return false;               // 6. Article
    if (! contactExists()) return false;                // 7. Contact
    if (! socialExists()) return false;                 // 8. Social media
    return true;
}

Algoritma Auto-Navigasi

Saat user kembali ke /setup, wizard otomatis menghitung langkah pertama yang belum selesai.

Cara Kerja

Backend kirim 8 pengecekan:        Frontend hitung step:
  [0] Super User ──── selesai ──► Step 1 selesai?
  [1] Geographic ──── selesai ─┐
  [2] Core Settings ─ selesai ─┤ Step 2 selesai? (semua 4)
  [3] Gender/Ethnic ─ selesai ─┤
  [4] Localization ── selesai ─┘
  [5] Schools ─────── pending ─┐
  [6] Articles ───── pending ──┤ Step 3 selesai? (semua 3)
  [7] About Config ── pending ─┘

                        ┌──────────┴──────────┐
                        ▼                     ▼
                   Semua selesai?       Ada yg pending?
                   return 5             return step #

Implementasi (computeInitialStep)

typescript
export function computeInitialStep(checks: CheckItem[]): number {
    if (checks.length === 0) return 1;
    if (checks[0]?.status !== 'completed') return 1;     // Super User

    const seedChecks = checks.slice(1, 5);
    if (seedChecks.some((c) => c.status !== 'completed')) return 2;  // Seed

    const aboutChecks = checks.slice(5, 8);
    if (aboutChecks.some((c) => c.status !== 'completed')) return 3; // About

    return 5;  // Done
}

Penting!

$state harus diinisialisasi LANGSUNG dengan nilai yang sudah dihitung:

svelte
// ✅ BENAR
let currentStep = $state(computeInitialStep(initialChecks));

// ❌ SALAH — menyebabkan flash render
let currentStep = $state(1);
$effect(() => { stepControl.configureFromChecks(initialChecks); });

Middleware

SetupMiddleware

File: app/Http/Middleware/Setup/SetupMiddleware.php

php
// /setup* dan /api/setup* selalu diizinkan
if ($request->is('setup*') || $request->is('api/setup*')) {
    return $next($request);
}
if (! IdentifierAppService::isSetup()) {
    return redirect('/setup');
}

MaintenanceMiddleware

File: app/Http/Middleware/Setup/MaintenanceMiddleware.php

php
// /setup*, /api/setup*, /portal/* bypass
if ($request->is('setup*') || $request->is('api/setup*') || $request->is('portal/*')) {
    return $next($request);
}
if (IdentifierAppService::isMaintenance()) {
    return Inertia::render('landing/UnderConstruction');
}
if (IdentifierAppService::isSeeder()) {
    return redirect('/portal');
}

Algoritma SSE Seed (Step 2)

Menggunakan Server-Sent Events untuk progress real-time.

Arsitektur

StepSeed (Index.svelte)

seedStore.startSeed()          ──► stores/seed.stores.ts

seedSseStore.stream()          ──► stores/seed-sse.stores.ts

seed.service.ts                ──► services/seed.service.ts

seed.api.ts                    ──► api/seed.api.ts

POST /api/setup/seed           ──► SSE stream

Event SSE

EventDataKeterangan
start{ total: 7 }Seeding dimulai
progress{ label, current, total, percent }Per-seeder
seederComplete{ label, current, total }Seeder selesai
checks{ checks: [...] }Status sistem terbaru
complete{ message, checks }Semua selesai
error{ message }Gagal

State Machine

IDLE → CONNECTING → STREAMING → COMPLETE
                         │          │
                         └── error  └── auto-next step

                           IDLE (retry)

Store Pattern (StateContainer<TData>)

Semua store menggunakan format dari @/types/state:

typescript
interface StateContainer<TData> {
    meta: { loading, initialized, updatedAt, errorAt };
    data: TData;
    errors: Record<string, string>;
}

Arsitektur Layer

Component (hanya presentasi)
    ▼ subscribe + panggil action
Store (manajemen state)
    ▼ delegasi ke
Service (logika bisnis)
    ▼ panggil
API (HTTP fetch)

Aturan Ketat

  • Component: DILARANG fetch() langsung, DILARANG parse SSE, DILARANG logika bisnis
  • Store: kelola state saja, delegasi logika ke service
  • Service: orchestrasi API calls, transform data
  • API: pure HTTP, tanpa logika bisnis

Struktur Modul

module/
├── Index.svelte          # Entry point — minimal, komposisi saja
├── api/                  # Fungsi fetch
├── parts/                # Sub-komponen (atomic design)
├── services/             # Logika bisnis
├── stores/               # State management
│   ├── aksi-modul.stores.ts
│   └── subdir/
│       ├── store-a.stores.ts
│       └── index.ts
├── types/                # TypeScript interfaces
└── __tests__/            # Vitest tests

Naming Conventions

LayerFormatContoh
Store file<aksi>-<modul>.stores.tsfetch-bulletin.stores.ts
Service file<modul>.service.tsbulletin.service.ts
API file<modul>.api.tsbulletin.api.ts
Type file<modul>.types.tsbulletin.types.ts
KomponenPascalCaseBulletinCard.svelte
Subfolder store<aksi>-<modul>/super-user-entry/

Frontend: camelCase. Komunikasi ke backend: snake_case.

Migration Runner

File: database/migrations/0000_runner_migration.php

  • Baca env MIGRATION_RUNNER_FOLDERS (default: v1_0_0,...)
  • Jalankan migrasi per subfolder versi
  • Support custom sort via sortMigrations() overrides
  • Output nama folder: echo "--- Migrating: v1_0_0 ---"

Multi-Bahasa (i18n)

  • File: lang/en.json dan lang/id.json
  • Import: import { t } from '@/lib/i18n.svelte'
  • Pattern key: <modul>.<bagian>.<key> (e.g., setup.seed.title)
  • DILARANG hardcode string display di component