Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ LONGITUDE_MAP = 115.046600
API_DATABASE_GABUNGAN_HOST=http://127.0.0.1:8001
SANCTUM_STATEFUL_DOMAINS=localhost

# DataSpasial - API Satu Data (App\Services\DataSpasialApiService)
# Base URL & token mengikuti pengaturan database gabungan (API_DATABASE_GABUNGAN_HOST
# dan Setting `database_gabungan_api_key`); TIDAK ada env khusus untuk keduanya.
# Batas waktu permintaan (detik) & masa cache baca (detik, 0 = nonaktif).
API_DATASPASIAL_TIMEOUT=10
API_DATASPASIAL_CACHE_TTL=300

# OTP Configuration
OTP_TOKEN_EXPIRES_MINUTES=5
OTP_MAX_VERIFICATION_ATTEMPTS=3
Expand Down
78 changes: 78 additions & 0 deletions app/Enums/DataSpasialReason.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace App\Enums;

use BenSampo\Enum\Enum;

/**
* Penyebab data DataSpasial tidak dapat diperoleh.
*
* CATATAN PENTING: enum ini hanya untuk keperluan diagnosa (log backend).
* `ModuleNotInstalled` dan `ServiceUnavailable` sengaja menghasilkan
* `DataSpasialStatus::Unavailable` yang sama sehingga UI tidak perlu
* membedakan keduanya maupun memetakan teks pesan dari API (FR-014).
*
* @method static static NotConfigured()
* @method static static Timeout()
* @method static static ConnectionFailed()
* @method static static ServiceUnavailable()
* @method static static ModuleNotInstalled()
* @method static static Unauthorized()
* @method static static SchemaVersionUnsupported()
* @method static static ClientError()
*/
final class DataSpasialReason extends Enum
{
/**
* URL dasar atau token API belum dikonfigurasi (FR-003).
*/
public const NotConfigured = 'not_configured';

/**
* API tidak menjawab dalam batas waktu (FR-004).
*/
public const Timeout = 'timeout';

/**
* Kegagalan koneksi: DNS, TCP, atau TLS.
*/
public const ConnectionFailed = 'connection_failed';

/**
* Status 5xx selain 503.
*/
public const ServiceUnavailable = 'http_5xx';

/**
* Status 503 pada endpoint DataSpasial: modul belum terpasang atau
* versinya tidak cocok (API-02 / #467).
*/
public const ModuleNotInstalled = 'module_not_installed';

/**
* Status 401 (token ditolak) atau 403 (ability tidak cukup).
*/
public const Unauthorized = 'unauthorized';

/**
* Status 400 dengan error_code SchemaVersionUnsupported.
*/
public const SchemaVersionUnsupported = 'schema_version_unsupported';

/**
* Status 4xx lainnya: 404, 409, 422.
*/
public const ClientError = 'client_error';

/**
* Pesan yang ditampilkan ke pengguna akhir untuk semua penyebab di atas.
* WAJIB generik — jangan pernah menyertakan detail teknis (FR-014/FR-016).
*/
public static function userMessage(): string
{
return config('dataspasial.unavailable_message')
?? 'Data sementara tidak tersedia. Silakan coba lagi nanti.';
}
}
36 changes: 36 additions & 0 deletions app/Enums/DataSpasialStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Enums;

use BenSampo\Enum\Enum;

/**
* Status hasil pemanggilan API Satu Data.
*
* Tiga nilai ini adalah pembeda utama antara "data memang kosong" dan
* "data tidak dapat diperoleh" (FR-005). View WAJIB bercabang berdasarkan
* nilai ini, bukan berdasarkan apakah array kosong.
*
* @method static static Data()
* @method static static Empty()
* @method static static Unavailable()
*/
final class DataSpasialStatus extends Enum
{
/**
* Respons sukses berisi minimal satu item.
*/
public const Data = 'data';

/**
* Respons sukses tanpa data (FR-015).
*/
public const Empty = 'empty';

/**
* Data tidak dapat diperoleh sementara (FR-014).
*/
public const Unavailable = 'unavailable';
}
37 changes: 37 additions & 0 deletions app/Enums/Modul.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,43 @@ final class Modul extends Enum
],
],
],
[
'text' => 'DataSpasial',
'icon' => 'fas fa-map',
'permission' => 'dataspasial',
'submenu' => [
[
'icon' => 'far fa-fw fa-circle',
'text' => 'Skema DataSpasial',
'url' => 'dataspasial/skema',
'permission' => 'dataspasial-skema',
],
[
'icon' => 'far fa-fw fa-circle',
'text' => 'Peta Gabungan',
'url' => 'dataspasial/peta',
'permission' => 'dataspasial-peta',
],
[
'icon' => 'far fa-fw fa-circle',
'text' => 'Dasbor DataSpasial',
'url' => 'dataspasial/dasbor',
'permission' => 'dataspasial-dasbor',
],
[
'icon' => 'far fa-fw fa-circle',
'text' => 'Impor Data',
'url' => 'dataspasial/impor',
'permission' => 'dataspasial-impor',
],
[
'icon' => 'far fa-fw fa-circle',
'text' => 'Monitoring',
'url' => 'dataspasial/monitoring',
'permission' => 'dataspasial-monitoring',
],
],
],
[
'text' => 'SOTK',
'icon' => 'fa fa-tags',
Expand Down
42 changes: 42 additions & 0 deletions app/Exceptions/DataSpasialUnavailableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace App\Exceptions;

use App\Enums\DataSpasialReason;
use Exception;
use Throwable;

/**
* Penanda internal bahwa data DataSpasial tidak dapat diperoleh sementara.
*
* Exception ini dipakai agar method internal di dalam service dapat keluar
* lebih awal tanpa menduplikasi penanganan kegagalan. Exception ini TIDAK
* PERNAH boleh lepas ke controller — pemanggil publik selalu memperoleh
* `App\Services\DataSpasialResult` dengan status `unavailable`.
*/
class DataSpasialUnavailableException extends Exception
{
public function __construct(
public readonly DataSpasialReason $reason,
public readonly string $endpoint = '',
public readonly int $httpStatus = 0,
?string $message = null,
?Throwable $previous = null
) {
parent::__construct(
$message ?? $reason->value,
0,
$previous
);
}

/**
* Pesan yang aman ditampilkan ke pengguna akhir (FR-014).
*/
public function userMessage(): string
{
return DataSpasialReason::userMessage();
}
}
38 changes: 38 additions & 0 deletions app/Http/Controllers/DataSpasial/DasborDataSpasialController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\DataSpasial;

use App\Services\DataSpasialResult;
use Illuminate\Http\Request;

/**
* Menu Dasbor DataSpasial.
*
* Kerangka menu pada fase fondasi ini. Agregat dan visualisasi penuh
* menjadi lingkup KAB-08 (OpenSID/OpenKab#1304).
*/
class DasborDataSpasialController extends DataSpasialController
{
protected function pageTitle(): string
{
return 'Dasbor DataSpasial';
}

protected function viewName(): string
{
return 'dataspasial.dasbor.index';
}

protected function fetch(Request $request): DataSpasialResult
{
return $this->dataspasial->ringkasan($request->only([
'sektor_id',
'periode',
'kode_kabupaten',
'kode_kecamatan',
'kode_desa',
]));
}
}
51 changes: 51 additions & 0 deletions app/Http/Controllers/DataSpasial/DataSpasialController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\DataSpasial;

use App\Http\Controllers\Controller;
use App\Services\DataSpasialApiService;
use App\Services\DataSpasialResult;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;

/**
* Kerangka dasar untuk kelima halaman menu DataSpasial.
*
* Aturan yang berlaku untuk semua halaman:
* - HANYA membaca lewat `DataSpasialApiService` (FR-002/SC-009);
* - controller TIDAK melempar exception HTTP ke view — kegagalan API sudah
* diterjemahkan menjadi `DataSpasialResult` berstatus `unavailable`
* (FR-014);
* - respons selalu HTTP 200 agar kerangka halaman & navigasi tetap utuh.
*/
abstract class DataSpasialController extends Controller
{
public function __construct(protected DataSpasialApiService $dataspasial) {}

/**
* Nama tampilan halaman untuk judul & breadcrumb.
*/
abstract protected function pageTitle(): string;

/**
* View kerangka halaman.
*/
abstract protected function viewName(): string;

/**
* Ambil data dari service. Method ini tidak pernah melempar exception.
*
* @param array<string, mixed> $filters
*/
abstract protected function fetch(Request $request): DataSpasialResult;

public function index(Request $request): View
{
return view($this->viewName(), [
'title' => $this->pageTitle(),
'result' => $this->fetch($request),
]);
}
}
36 changes: 36 additions & 0 deletions app/Http/Controllers/DataSpasial/ImporDataController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\DataSpasial;

use App\Services\DataSpasialResult;
use Illuminate\Http\Request;

/**
* Menu Impor Data (Operator Impor).
*
* Kerangka menu pada fase fondasi ini. Alur impor lengkap (template, validasi
* per baris, ringkasan ditolak/konflik) menjadi lingkup KAB-04.
*/
class ImporDataController extends DataSpasialController
{
protected function pageTitle(): string
{
return 'Impor Data';
}

protected function viewName(): string
{
return 'dataspasial.impor.index';
}

protected function fetch(Request $request): DataSpasialResult
{
return $this->dataspasial->listFasilitas($request->only([
'sektor_id',
'skema_versi_id',
'kode_desa',
]));
}
}
39 changes: 39 additions & 0 deletions app/Http/Controllers/DataSpasial/MonitoringController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\DataSpasial;

use App\Services\DataSpasialResult;
use Illuminate\Http\Request;

/**
* Menu Monitoring.
*
* Kerangka menu pada fase fondasi ini. Daftar entri audit lengkap menjadi
* lingkup KAB-08.
*/
class MonitoringController extends DataSpasialController
{
protected function pageTitle(): string
{
return 'Monitoring';
}

protected function viewName(): string
{
return 'dataspasial.monitoring.index';
}

protected function fetch(Request $request): DataSpasialResult
{
return $this->dataspasial->adopsi($request->only([
'sektor_id',
'skema_versi_id',
'status_adopsi',
'kode_kabupaten',
'kode_kecamatan',
'kode_desa',
]));
}
}
Loading
Loading