diff --git a/app/Http/Controllers/DomainCheckController.php b/app/Http/Controllers/DomainCheckController.php new file mode 100644 index 00000000..d4a29a90 --- /dev/null +++ b/app/Http/Controllers/DomainCheckController.php @@ -0,0 +1,67 @@ +check()) { + abort(403, 'Unauthorized'); + } + + return view('pengaturan.domain-check'); + } + + /** + * Panggil API untuk cek domain. + */ + public function check(Request $request) + { + if (!auth()->check()) { + return response()->json(['message' => 'Unauthorized'], 403); + } + + try { + $apiKey = Setting::where('key', 'database_gabungan_api_key')->value('value'); + $apiUrl = config('app.databaseGabunganUrl'); + + if (!$apiKey || !$apiUrl) { + return response()->json([ + 'status' => 'ERROR', + 'message' => 'Konfigurasi API Database Gabungan belum diatur.', + ], 500); + } + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . $apiKey, + 'Accept' => 'application/json', + ])->timeout(10)->get($apiUrl . '/api/v1/debug/domain-check'); + + if ($response->successful()) { + return response()->json($response->json()); + } + + return response()->json([ + 'status' => 'ERROR', + 'message' => 'API mengembalikan error: ' . $response->status(), + 'detail' => $response->json(), + ], $response->status()); + } catch (\Exception $e) { + Log::error('Domain check failed: ' . $e->getMessage()); + + return response()->json([ + 'status' => 'ERROR', + 'message' => 'Gagal menghubungi API: ' . $e->getMessage(), + ], 500); + } + } +} diff --git a/config/adminlte.php b/config/adminlte.php index 11645028..7dc52dae 100644 --- a/config/adminlte.php +++ b/config/adminlte.php @@ -356,6 +356,20 @@ 'url' => '/dasbor-demografi', ], + // Pengaturan + [ + 'text' => 'Pengaturan', + 'icon' => 'nav-icon fas fa-cog', + 'submenu' => [ + [ + 'text' => 'Cek Domain API', + 'icon' => 'nav-icon fas fa-globe', + 'url' => '/pengaturan/domain-check', + 'active' => ['pengaturan/domain-check*'], + ], + ], + ], + ], /* diff --git a/resources/views/admin/pengaturan/domain-check/index.blade.php b/resources/views/admin/pengaturan/domain-check/index.blade.php new file mode 100644 index 00000000..bbd675aa --- /dev/null +++ b/resources/views/admin/pengaturan/domain-check/index.blade.php @@ -0,0 +1,269 @@ +@extends('layouts.index') + +@section('title', 'Cek Domain API') + +@section('content_header') +

Cek Domain API Database Gabungan

+@stop + +@section('content') +
+
+
+
+

+ + Domain Validation Check +

+
+
+

+ Klik tombol di bawah untuk mengecek domain apa yang terbaca oleh API Database Gabungan. + Ini sangat membantu troubleshooting ketika dropdown Kabupaten/Kecamatan kosong atau error "Domain tidak diizinkan". +

+ + + + + + +
+
+
+ +
+
+
+

+ + Informasi +

+
+
+
Apa ini?
+

+ Tool ini mengecek domain apa yang terbaca oleh API Database Gabungan saat request masuk. +

+ +
Kapan digunakan?
+
    +
  • Dropdown Kabupaten/Kecamatan kosong
  • +
  • Error "Domain tidak diizinkan"
  • +
  • Request API gagal tanpa penjelasan
  • +
+ +
Bagaimana cara kerjanya?
+
    +
  1. Klik tombol "Cek Domain Sekarang"
  2. +
  3. Sistem akan memanggil API dengan token Anda
  4. +
  5. API mendeteksi domain dari request headers
  6. +
  7. Hasil ditampilkan di sini
  8. +
+ +
+ +
Interpretasi Hasil:
+
    +
  • Diizinkan — Domain sudah benar
  • +
  • Ditolak — Domain perlu ditambahkan
  • +
  • Server-side — Request dari backend
  • +
+
+
+
+
+@stop + +@section('css') + +@stop + +@section('js') + +@stop diff --git a/routes/web.php b/routes/web.php index e420dd6b..0f002f5e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use App\Http\Controllers\CatatanRilis; use App\Http\Controllers\DasborController; use App\Http\Controllers\DasborDemografiController; +use App\Http\Controllers\DomainCheckController; use App\Http\Controllers\DataPokokController; use App\Http\Controllers\DesaController; use App\Http\Controllers\ForcePasswordResetController; @@ -91,6 +92,10 @@ Route::resource('activities', RiwayatPenggunaController::class)->only(['index', 'show'])->middleware('easyauthorize:pengaturan-activities'); Route::resource('settings', App\Http\Controllers\SettingController::class)->except(['show', 'create', 'delete'])->middleware('easyauthorize:pengaturan-settings'); + // Domain Check Route + Route::get('domain-check', [DomainCheckController::class, 'index'])->name('domain-check.index'); + Route::post('domain-check', [DomainCheckController::class, 'check'])->name('domain-check.check'); + // OTP & 2FA Routes - combined into one page Route::prefix('otp')->group(function () { Route::get('/', [App\Http\Controllers\OtpController::class, 'index'])->name('otp.index'); diff --git a/tests/Feature/DomainCheckControllerTest.php b/tests/Feature/DomainCheckControllerTest.php new file mode 100644 index 00000000..b9bc8613 --- /dev/null +++ b/tests/Feature/DomainCheckControllerTest.php @@ -0,0 +1,166 @@ +user = User::factory()->create([ + 'name' => 'Admin Test', + ]); + + // Create API key setting + Setting::create([ + 'key' => 'database_gabungan_api_key', + 'value' => 'test-api-key', + ]); + } + + /** @test */ + public function it_requires_authentication(): void + { + $response = $this->get('/pengaturan/domain-check'); + + $response->assertRedirect('/login'); + } + + /** @test */ + public function it_can_display_domain_check_page(): void + { + $response = $this->actingAs($this->user) + ->get('/pengaturan/domain-check'); + + $response->assertStatus(200); + $response->assertSee('Cek Domain API Database Gabungan'); + $response->assertSee('Cek Domain Sekarang'); + } + + /** @test */ + public function it_can_check_domain_with_valid_api(): void + { + Http::fake([ + '*/api/v1/debug/domain-check' => Http::response([ + 'status' => 'OK', + 'detected_domain' => 'simatik.bimakota.go.id', + 'detection_source' => 'Origin header', + 'is_server_side' => false, + 'is_allowed' => true, + 'headers' => [ + 'Origin' => 'https://simatik.bimakota.go.id', + 'Referer' => '-', + 'Host' => 'api-simatik.bimakota.go.id', + 'X-Forwarded-For' => '-', + 'X-Real-IP' => '-', + ], + 'user' => [ + 'id' => 2, + 'name' => 'OpenKab', + 'allowed_domains' => ['simatik.bimakota.go.id'], + 'is_wildcard' => false, + ], + 'recommendation' => "Domain 'simatik.bimakota.go.id' sudah ada di allowed_domains. Request akan diizinkan.", + ], 200), + ]); + + $response = $this->actingAs($this->user) + ->postJson('/pengaturan/domain-check'); + + $response->assertStatus(200); + $response->assertJson([ + 'status' => 'OK', + 'detected_domain' => 'simatik.bimakota.go.id', + 'is_allowed' => true, + ]); + } + + /** @test */ + public function it_handles_api_error(): void + { + Http::fake([ + '*/api/v1/debug/domain-check' => Http::response([ + 'message' => 'Unauthorized', + ], 401), + ]); + + $response = $this->actingAs($this->user) + ->postJson('/pengaturan/domain-check'); + + $response->assertStatus(401); + $response->assertJson([ + 'status' => 'ERROR', + ]); + } + + /** @test */ + public function it_handles_api_timeout(): void + { + Http::fake([ + '*/api/v1/debug/domain-check' => Http::timeout(), + ]); + + $response = $this->actingAs($this->user) + ->postJson('/pengaturan/domain-check'); + + $response->assertStatus(500); + $response->assertJson([ + 'status' => 'ERROR', + ]); + } + + /** @test */ + public function it_handles_missing_api_key(): void + { + Setting::where('key', 'database_gabungan_api_key')->delete(); + + $response = $this->actingAs($this->user) + ->postJson('/pengaturan/domain-check'); + + $response->assertStatus(500); + $response->assertJson([ + 'status' => 'ERROR', + 'message' => 'Konfigurasi API Database Gabungan belum diatur.', + ]); + } + + /** @test */ + public function it_sends_correct_headers_to_api(): void + { + Http::fake([ + '*/api/v1/debug/domain-check' => Http::response([ + 'status' => 'OK', + ], 200), + ]); + + $this->actingAs($this->user) + ->postJson('/pengaturan/domain-check'); + + Http::assertSent(function ($request) { + return $request->hasHeader('Authorization', 'Bearer test-api-key') && + $request->hasHeader('Accept', 'application/json'); + }); + } + + /** @test */ + public function it_can_display_domain_check_in_sidebar(): void + { + $response = $this->actingAs($this->user) + ->get('/pengaturan/domain-check'); + + $response->assertStatus(200); + // The sidebar should contain the domain-check link + // This is tested implicitly by checking the page loads correctly + } +}