Initial commit BIJ CI4

This commit is contained in:
BIJ Dev
2026-04-21 05:49:17 +07:00
commit fa38ac6b24
13170 changed files with 866701 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use App\Controllers\BaseController;
use App\Services\Admin\AdminApiService;
use App\Services\Admin\AdminExtraApiService;
use App\Services\AdminAuditService;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Dasar API `/api/admin/*`: wajib **sesi panel admin** + token yang sama dengan sesi;
* RBAC per fitur (`canAccess`). Tanpa cookie sesi, panggilan hanya dengan `?token=` ditolak.
*
* Audit: setelah otorisasi sukses, controller anak memanggil {@see auditAuthorized()}.
* Percobaan ditolak dicatat ke DB + `log_message`.
*/
abstract class BaseAdminApiController extends BaseController
{
protected AdminApiService $adminApi;
protected AdminExtraApiService $adminExtra;
private AdminAuditService $audit;
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger): void
{
parent::initController($request, $response, $logger);
helper('rbac');
$this->adminApi = new AdminApiService();
$this->adminExtra = new AdminExtraApiService();
$this->audit = new AdminAuditService();
}
/**
* @param array<string, mixed> $payload
*/
protected function auditAuthorized(string $action, array $actor, array $payload = []): void
{
$payload['actor'] = $this->actorAuditSummary($actor);
$this->audit->log($action, $payload);
}
/**
* @param array<string, mixed> $actor
*
* @return array<string, mixed>
*/
private function actorAuditSummary(array $actor): array
{
return [
'id_pegawai' => $actor['id_pegawai'] ?? null,
'nip' => $actor['nip'] ?? null,
'nama_lengkap' => $actor['nama_lengkap'] ?? null,
];
}
/**
* Ringkasan GET/POST untuk payload audit (dibatasi ukuran).
*
* @return array<string, mixed>
*/
protected function auditRequestParams(): array
{
$get = $this->request->getGet();
$post = $this->request->getPost();
return [
'query' => is_array($get) ? $this->truncateParamMap($get, 40) : [],
'post' => is_array($post) ? $this->truncateParamMap($post, 40) : [],
];
}
/**
* @param array<string, mixed> $map
*
* @return array<string, mixed>
*/
private function truncateParamMap(array $map, int $maxKeys): array
{
$i = 0;
$out = [];
foreach ($map as $k => $v) {
if ($i++ >= $maxKeys) {
$out['_truncated'] = true;
break;
}
if (is_scalar($v) || $v === null) {
$out[(string) $k] = $v;
} elseif (is_array($v)) {
$out[(string) $k] = '[array]';
}
}
return $out;
}
/**
* @param array<string, mixed> $payload
*/
protected function respond(array $payload, int $code = 200): ResponseInterface
{
return $this->response->setStatusCode($code)->setJSON($payload);
}
protected function extractToken(): string
{
$t = $this->request->getGet('token')
?? $this->request->getPost('token')
?? $this->request->getHeaderLine('X-Admin-Token');
return is_string($t) ? trim($t) : '';
}
/**
* Sesi admin panel aktif + token permintaan sama dengan `admin_mobile_token` di sesi.
*
* @return array{actor: array<string, mixed>|null, response: null}|array{actor: null, response: ResponseInterface}
*/
protected function requireAdminSessionBoundToken(): array
{
$sess = session();
$sessTok = $sess->get('admin_mobile_token');
if (! is_string($sessTok) || $sessTok === '') {
$this->logAdminSecurityDenied('no_admin_session', 'unauthorized');
return [
'actor' => null,
'response' => $this->respond(['status' => 0, 'pesan' => 'Unauthorized'], 401),
];
}
$reqTok = $this->extractToken();
if ($reqTok === '' || $reqTok !== $sessTok) {
$this->logAdminSecurityDenied('token_not_bound_to_session', 'unauthorized');
return [
'actor' => null,
'response' => $this->respond(['status' => 0, 'pesan' => 'Unauthorized'], 401),
];
}
$actor = $this->adminApi->actorFromToken($reqTok);
if ($actor === null) {
$this->logAdminSecurityDenied('invalid_token_actor', 'unauthorized');
return [
'actor' => null,
'response' => $this->respond(['status' => 0, 'pesan' => 'Token tidak valid'], 403),
];
}
return ['actor' => $actor, 'response' => null];
}
/**
* RBAC fitur — memakai `admin_ion_groups` + `Config\AdminAccess` (sama lapisan web).
*/
protected function requireAdminFeature(string $feature): ?ResponseInterface
{
if (canAccess($feature)) {
return null;
}
$this->logAdminSecurityDenied('forbidden_feature:' . $feature, 'forbidden');
return $this->respond(['status' => 0, 'pesan' => 'Forbidden'], 403);
}
/**
* Gabungan autentikasi sesi + RBAC satu pintu untuk endpoint admin API.
*
* @return array{actor: array<string, mixed>|null, response: null}|array{actor: null, response: ResponseInterface}
*/
protected function requireAdminApiAccess(string $feature): array
{
$auth = $this->requireAdminSessionBoundToken();
if ($auth['response'] !== null) {
return $auth;
}
$deny = $this->requireAdminFeature($feature);
if ($deny !== null) {
return ['actor' => $auth['actor'], 'response' => $deny];
}
return ['actor' => $auth['actor'], 'response' => null];
}
/**
* Filter cabang untuk grup Ion `supervisor`: data pegawai/cuti/presensi dibatasi ke `pegawai.kantor`
* yang sama dengan baris pegawai pemegang token (bukan dari jabatan).
*
* @return array{scoped: bool, kantor_id: int|null} scoped=true & kantor_id=null → pegawai supervisor belum punya kantor
*/
protected function cabangScopeFromActor(?array $actor): array
{
if ($actor === null || ! rbac_enforce_ion()) {
return ['scoped' => false, 'kantor_id' => null];
}
if (hasRole('webmaster') || hasRole('hrd')) {
return ['scoped' => false, 'kantor_id' => null];
}
if (! hasRole('supervisor')) {
return ['scoped' => false, 'kantor_id' => null];
}
$kid = (int) ($actor['kantor'] ?? 0);
if ($kid <= 0) {
return ['scoped' => true, 'kantor_id' => null];
}
return ['scoped' => true, 'kantor_id' => $kid];
}
/**
* Supervisor wajib punya `kantor` pada baris pegawai (token) agar scope cabang terdefinisi.
*/
protected function denyIfSupervisorCabangInvalid(array $scope): ?ResponseInterface
{
if ($scope['scoped'] && $scope['kantor_id'] === null) {
return $this->respond([
'status' => 0,
'pesan' => 'Akun supervisor belum memiliki cabang (kantor) pada data pegawai. Hubungi HRD untuk mengisi kolom kantor.',
], 403);
}
return null;
}
/** @return int|null null = tanpa filter cabang */
protected function cabangKantorIdForQueries(array $scope): ?int
{
return $scope['scoped'] ? $scope['kantor_id'] : null;
}
/**
* Setelah {@see requireAdminApiAccess()} sukses: validasi kantor supervisor & siapkan filter cabang.
*
* @return array{kid: int|null, response: ResponseInterface|null}
*/
protected function cabangKantorAfterAuth(?array $actor): array
{
$scope = $this->cabangScopeFromActor($actor);
if (($deny = $this->denyIfSupervisorCabangInvalid($scope)) !== null) {
return ['kid' => null, 'response' => $deny];
}
return ['kid' => $this->cabangKantorIdForQueries($scope), 'response' => null];
}
private function logAdminSecurityDenied(string $reason, string $outcome): void
{
$ip = $this->request->getIPAddress();
$path = $this->request->getUri()->getPath();
$when = date('c');
log_message('warning', "[api/admin] denied={$reason} outcome={$outcome} ip={$ip} path={$path} at={$when}");
$action = $outcome === 'forbidden' ? 'api.admin.security.forbidden' : 'api.admin.security.unauthorized';
$this->audit->log($action, [
'reason' => $reason,
'__outcome' => $outcome,
]);
}
}

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Master data perusahaan (kantor, unit kerja, golongan, jabatan, berita).
*/
class CompanyDataApiController extends BaseAdminApiController
{
public function kantor(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.kantor.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->kantorList());
}
public function kantorSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_kantor'] ?? 0);
$this->auditAuthorized('api.admin.company.kantor.save', $auth['actor'], ['id_kantor' => $id ?: null]);
return $this->respond($this->adminExtra->kantorSave($post, $id > 0 ? $id : null));
}
public function kantorDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.kantor.delete', $auth['actor'], ['id_kantor' => $id]);
return $this->respond($this->adminExtra->kantorDelete($id));
}
public function unitKerja(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.unit_kerja.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->unitKerjaList());
}
public function unitKerjaSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_unit_kerja'] ?? 0);
$this->auditAuthorized('api.admin.company.unit_kerja.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->unitKerjaSave($post, $id > 0 ? $id : null));
}
public function unitKerjaDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.unit_kerja.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->unitKerjaDelete($id));
}
public function golongan(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.golongan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->golonganList());
}
public function golonganSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_golongan'] ?? 0);
$this->auditAuthorized('api.admin.company.golongan.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->golonganSave($post, $id > 0 ? $id : null));
}
public function golonganDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.golongan.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->golonganDelete($id));
}
public function jabatan(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.jabatan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->jabatanList());
}
public function jabatanSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_jabatan'] ?? 0);
$this->auditAuthorized('api.admin.company.jabatan.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->jabatanSave($post, $id > 0 ? $id : null));
}
public function jabatanDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.jabatan.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->jabatanDelete($id));
}
public function berita(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.berita.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->beritaList());
}
public function beritaSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_berita'] ?? 0);
$this->auditAuthorized('api.admin.company.berita.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->beritaSave($post, $id > 0 ? $id : null));
}
public function beritaDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('perusahaan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.company.berita.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->beritaDelete($id));
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class CutiController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('cuti');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.cuti.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$status = (string) ($this->request->getGet('status') ?? 'Waiting');
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
$perPage = max(5, min(200, (int) ($this->request->getGet('per_page') ?? 30)));
return $this->respond($this->adminApi->cutiList($status, $page, $perPage, $cb['kid']));
}
public function show(?string $id = null): ResponseInterface
{
$auth = $this->requireAdminApiAccess('cuti');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.cuti.show', $auth['actor'], [
'request' => $this->auditRequestParams(),
'id' => $id,
]);
$idInt = (int) ($id ?? 0);
if ($idInt <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
}
return $this->respond($this->adminApi->cutiShow($idInt, $cb['kid']));
}
public function approve(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('cuti');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$id = (int) ($this->request->getPost('id_cuti') ?? 0);
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'id_cuti wajib'], 400);
}
$this->auditAuthorized('api.admin.cuti.approve', $auth['actor'], [
'cuti' => ['id_cuti' => $id],
]);
return $this->respond($this->adminApi->cutiApprove($id, $cb['kid']));
}
public function reject(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('cuti');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$id = (int) ($this->request->getPost('id_cuti') ?? 0);
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'id_cuti wajib'], 400);
}
$alasan = (string) ($this->request->getPost('alasan_tolak') ?? '');
$this->auditAuthorized('api.admin.cuti.reject', $auth['actor'], [
'cuti' => [
'id_cuti' => $id,
'alasan_tolak' => function_exists('mb_substr') ? mb_substr($alasan, 0, 500) : substr($alasan, 0, 500),
],
]);
return $this->respond($this->adminApi->cutiReject($id, $alasan, $cb['kid']));
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class DashboardController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('dashboard');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.dashboard.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$soloPegawaiId = null;
if (! rbac_enforce_ion()) {
$soloPegawaiId = (int) ($auth['actor']['id_pegawai'] ?? 0);
if ($soloPegawaiId <= 0) {
$soloPegawaiId = null;
}
}
return $this->respond($this->adminApi->dashboardHome($cb['kid'], $soloPegawaiId));
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Utilitas: daftar file backup SQL di writable, buat backup, hapus file.
*/
class DatabaseBackupApiController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('utilitas');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.backup.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->backupListFiles());
}
public function run(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('utilitas');
if ($auth['response'] !== null) {
return $auth['response'];
}
$also = (string) ($this->request->getPost('save_latest') ?? '') === '1';
$this->auditAuthorized('api.admin.backup.run', $auth['actor'], [
'save_latest' => $also,
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminExtra->backupRun($also));
}
public function delete(string $file): ResponseInterface
{
$auth = $this->requireAdminApiAccess('utilitas');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.backup.delete', $auth['actor'], ['file' => $file]);
return $this->respond($this->adminExtra->backupDelete($file));
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class LaporanController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('laporan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.laporan.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$today = date('Y-m-d');
$dari = (string) ($this->request->getGet('dari') ?? $today);
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
if (strtotime($dari) === false || strtotime($sampai) === false) {
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
}
if ($dari > $sampai) {
[$dari, $sampai] = [$sampai, $dari];
}
return $this->respond($this->adminApi->laporanSummary($dari, $sampai, $cb['kid']));
}
/**
* Laporan cuti rentang tanggal (parity CI3 laporan/fcuti — data tabel).
*/
public function cutiRentang(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('laporan');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.laporan.cuti_rentang', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$today = date('Y-m-d');
$dari = (string) ($this->request->getGet('dari') ?? $today);
$sampai = (string) ($this->request->getGet('sampai') ?? $today);
if (strtotime($dari) === false || strtotime($sampai) === false) {
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
}
if ($dari > $sampai) {
[$dari, $sampai] = [$sampai, $dari];
}
return $this->respond($this->adminExtra->laporanCutiRentang($dari, $sampai, $cb['kid']));
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Panel Ion Auth: daftar pengguna admin, grup, buat user, reset password.
*/
class PanelUsersApiController extends BaseAdminApiController
{
public function users(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.panel.users.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->adminUsersList());
}
public function userShow(?string $id = null): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$idInt = (int) ($id ?? 0);
if ($idInt <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
}
$this->auditAuthorized('api.admin.panel.users.show', $auth['actor'], [
'request' => $this->auditRequestParams(),
'user_id' => $idInt,
]);
return $this->respond($this->adminExtra->adminUserShow($idInt));
}
public function userUpdate(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
}
$this->auditAuthorized('api.admin.panel.users.update', $auth['actor'], [
'user_id' => $id,
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminExtra->adminUserUpdate($id, $this->request->getPost()));
}
public function groups(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.panel.groups.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->adminGroupsList());
}
public function groupCreate(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.panel.groups.create', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->adminGroupCreate($this->request->getPost()));
}
public function groupUpdate(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID grup tidak valid'], 400);
}
$this->auditAuthorized('api.admin.panel.groups.update', $auth['actor'], [
'group_id' => $id,
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminExtra->adminGroupUpdate($id, $this->request->getPost()));
}
public function groupDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID grup tidak valid'], 400);
}
$this->auditAuthorized('api.admin.panel.groups.delete', $auth['actor'], ['group_id' => $id]);
return $this->respond($this->adminExtra->adminGroupDelete($id));
}
public function userCreate(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.panel.users.create', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminExtra->adminUserCreate($this->request->getPost()));
}
public function userResetPassword(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('panel');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.panel.users.reset_password', $auth['actor'], [
'user_id' => $id,
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminExtra->adminUserResetPassword($id, $this->request->getPost()));
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class PegawaiController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.pegawai.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$page = (int) ($this->request->getGet('page') ?? 1);
$perPage = (int) ($this->request->getGet('per_page') ?? 20);
$search = (string) ($this->request->getGet('q') ?? '');
return $this->respond($this->adminApi->pegawaiList($page, $perPage, $search, $cb['kid']));
}
public function show(?string $id = null): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.pegawai.show', $auth['actor'], [
'request' => $this->auditRequestParams(),
'id' => $id,
]);
$idInt = (int) ($id ?? 0);
if ($idInt <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
}
return $this->respond($this->adminApi->pegawaiShow($idInt, $cb['kid']));
}
public function create(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai_tambah');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$input = $this->normalizePegawaiInput($this->request->getPost());
$this->auditAuthorized('api.admin.pegawai.create', $auth['actor'], [
'pegawai' => $input,
]);
return $this->respond($this->adminApi->pegawaiCreate($input, $cb['kid']));
}
public function update(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
}
$input = $this->normalizePegawaiInput($this->request->getPost());
$this->auditAuthorized('api.admin.pegawai.update', $auth['actor'], [
'pegawai' => array_merge(['id_pegawai' => $id], $input),
]);
return $this->respond($this->adminApi->pegawaiUpdate($id, $input, $cb['kid']));
}
public function delete(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
}
$this->auditAuthorized('api.admin.pegawai.delete', $auth['actor'], [
'pegawai' => ['id_pegawai' => $id],
]);
return $this->respond($this->adminApi->pegawaiDelete($id, $cb['kid']));
}
public function resetPassword(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('pegawai');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$id = (int) ($this->request->getPost('id_pegawai') ?? 0);
if ($id <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'id_pegawai wajib'], 400);
}
$this->auditAuthorized('api.admin.pegawai.reset_password', $auth['actor'], [
'pegawai' => ['id_pegawai' => $id],
]);
return $this->respond($this->adminApi->pegawaiResetPassword($id, $cb['kid']));
}
/**
* @param array<string, mixed> $post
*
* @return array<string, scalar|null>
*/
private function normalizePegawaiInput(array $post): array
{
$out = [];
foreach ($post as $k => $v) {
if (is_scalar($v) || $v === null) {
$out[(string) $k] = $v;
}
}
return $out;
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class PresensiController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
$today = date('Y-m-d');
$dari = (string) ($this->request->getGet('tanggal_dari') ?? $today);
$sampai = (string) ($this->request->getGet('tanggal_sampai') ?? $today);
if (strtotime($dari) === false || strtotime($sampai) === false) {
return $this->respond(['status' => 0, 'pesan' => 'Format tanggal tidak valid'], 400);
}
if ($dari > $sampai) {
[$dari, $sampai] = [$sampai, $dari];
}
$page = (int) ($this->request->getGet('page') ?? 1);
$perPage = (int) ($this->request->getGet('per_page') ?? 30);
$q = (string) ($this->request->getGet('q') ?? '');
return $this->respond($this->adminApi->presensiList($dari, $sampai, $page, $perPage, $q, $cb['kid']));
}
public function show(?string $id = null): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.show', $auth['actor'], [
'request' => $this->auditRequestParams(),
'id' => $id,
]);
$idInt = (int) ($id ?? 0);
if ($idInt <= 0) {
return $this->respond(['status' => 0, 'pesan' => 'ID tidak valid'], 400);
}
return $this->respond($this->adminApi->presensiShow($idInt, $cb['kid']));
}
}

View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Presensi: tugas luar, lembur, libur, jadwal, aktivitas harian.
*/
class PresensiToolsApiController extends BaseAdminApiController
{
public function dilapangan(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.dilapangan.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->dilapanganList($cb['kid']));
}
public function dilapanganSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_dilapangan'] ?? 0);
$this->auditAuthorized('api.admin.presensi.dilapangan.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->dilapanganSave($post, $id > 0 ? $id : null, $cb['kid']));
}
public function dilapanganDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.dilapangan.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->dilapanganDelete($id, $cb['kid']));
}
public function lembur(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.lembur.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->lemburList($cb['kid']));
}
public function lemburSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_lembur'] ?? 0);
$this->auditAuthorized('api.admin.presensi.lembur.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->lemburSave($post, $id > 0 ? $id : null, $cb['kid']));
}
public function lemburDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.lembur.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->lemburDelete($id, $cb['kid']));
}
public function libur(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_libur');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.presensi.libur.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->liburList());
}
public function liburSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_libur');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_libur'] ?? 0);
$this->auditAuthorized('api.admin.presensi.libur.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->liburSave($post, $id > 0 ? $id : null));
}
public function liburDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_libur');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.presensi.libur.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->liburDelete($id));
}
public function jadwal(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_jadwal');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.presensi.jadwal.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->jadwalList());
}
public function jadwalSave(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_jadwal');
if ($auth['response'] !== null) {
return $auth['response'];
}
$post = $this->request->getPost();
$id = (int) ($post['id_jadwal'] ?? 0);
$this->auditAuthorized('api.admin.presensi.jadwal.save', $auth['actor'], ['id' => $id ?: null]);
return $this->respond($this->adminExtra->jadwalSave($post, $id > 0 ? $id : null));
}
public function jadwalDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi_jadwal');
if ($auth['response'] !== null) {
return $auth['response'];
}
$this->auditAuthorized('api.admin.presensi.jadwal.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->jadwalDelete($id));
}
public function aktivitas(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
$perPage = max(5, min(100, (int) ($this->request->getGet('per_page') ?? 20)));
$this->auditAuthorized('api.admin.presensi.aktivitas.list', $auth['actor'], ['request' => $this->auditRequestParams()]);
return $this->respond($this->adminExtra->aktifitasList($page, $perPage, $cb['kid']));
}
public function aktivitasDelete(int $id): ResponseInterface
{
$auth = $this->requireAdminApiAccess('presensi');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.presensi.aktivitas.delete', $auth['actor'], ['id' => $id]);
return $this->respond($this->adminExtra->aktifitasDelete($id, $cb['kid']));
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Controllers\Api\Admin;
use CodeIgniter\HTTP\ResponseInterface;
class ReferenceController extends BaseAdminApiController
{
public function index(): ResponseInterface
{
$auth = $this->requireAdminApiAccess('references');
if ($auth['response'] !== null) {
return $auth['response'];
}
$cb = $this->cabangKantorAfterAuth($auth['actor']);
if ($cb['response'] !== null) {
return $cb['response'];
}
$this->auditAuthorized('api.admin.references.index', $auth['actor'], [
'request' => $this->auditRequestParams(),
]);
return $this->respond($this->adminApi->references($cb['kid']));
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Libraries\ApiParityLogger;
use App\Libraries\LegacyUtf8Encoder;
use App\Services\Mobile\MobileJsonService;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Pengganti CI3 `application/controllers/Json.php`.
*
* URL CI4: POST /api/mobile/{method}
* URL CI3: POST /index.php/json/{method}
*
* @see docs/migration/json_api_map.md
*/
class MobileJsonController extends BaseController
{
protected MobileJsonService $mobileJson;
public function initController(\CodeIgniter\HTTP\RequestInterface $request, ResponseInterface $response, \Psr\Log\LoggerInterface $logger): void
{
parent::initController($request, $response, $logger);
$this->mobileJson = new MobileJsonService();
$this->response->setHeader('Access-Control-Allow-Origin', '*');
if (ApiParityLogger::enabled()) {
ApiParityLogger::logRequest($this->request);
}
}
/**
* @param array<string, mixed> $payload
*/
private function respondLegacy(array $payload): ResponseInterface
{
$body = LegacyUtf8Encoder::utf8ize($payload);
// CI3 memakai json_encode() default (tanpa JSON_UNESCAPED_UNICODE / PRETTY_PRINT).
$json = json_encode($body, 0, 512);
if ($json === false) {
$json = '{"status":0,"pesan":"JSON_ENCODE_ERROR"}';
}
if (ApiParityLogger::enabled()) {
ApiParityLogger::logResponse($json);
}
return $this->response->setStatusCode(200)
->setContentType('application/json', 'UTF-8')
->setBody($json);
}
public function login_w_token(): ResponseInterface
{
$token = (string) $this->request->getPost('token');
return $this->respondLegacy($this->mobileJson->loginWToken($token));
}
public function login(): ResponseInterface
{
$user = (string) $this->request->getPost('username');
$pass = (string) $this->request->getPost('password');
return $this->respondLegacy($this->mobileJson->login($user, $pass));
}
public function profil(): ResponseInterface
{
$token = (string) $this->request->getPost('token');
return $this->respondLegacy($this->mobileJson->profil($token));
}
public function save_cuti(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->saveCuti(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('nama_photo'),
(string) $this->request->getPost('photo'),
(string) $this->request->getPost('tanggal'),
(string) $this->request->getPost('alasan'),
(string) $this->request->getPost('tipe'),
));
}
public function batalkan_cuti(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->batalkanCuti(
(string) $this->request->getPost('token'),
$this->request->getPost('id'),
));
}
public function save_aktifitas(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->saveAktifitas(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('nama_photo'),
(string) $this->request->getPost('photo'),
(string) $this->request->getPost('tanggal'),
(string) $this->request->getPost('deksripsi'),
));
}
public function save_masuk(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->saveMasuk(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('nama_photo'),
(string) $this->request->getPost('photo'),
(string) $this->request->getPost('lat'),
(string) $this->request->getPost('lng'),
(string) $this->request->getPost('jarak'),
));
}
public function save_pulang(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->savePulang(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('nama_photo'),
(string) $this->request->getPost('photo'),
(string) $this->request->getPost('lat'),
(string) $this->request->getPost('lng'),
(string) $this->request->getPost('jarak'),
));
}
public function save_istirahat(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->saveIstirahat(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('mulai'),
(string) $this->request->getPost('selesai'),
));
}
public function presensi_today(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->presensiToday((string) $this->request->getPost('token')));
}
public function presensi(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->presensi((string) $this->request->getPost('token')));
}
public function daftar_today(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->daftarToday((string) $this->request->getPost('token')));
}
public function berita(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->berita(
(string) $this->request->getPost('token'),
$this->request->getPost('dari'),
$this->request->getPost('jumlah'),
));
}
public function cuti(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->cuti(
(string) $this->request->getPost('token'),
$this->request->getPost('dari'),
$this->request->getPost('jumlah'),
));
}
public function lembur(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->lembur(
(string) $this->request->getPost('token'),
$this->request->getPost('dari'),
$this->request->getPost('jumlah'),
));
}
public function libur(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->libur((string) $this->request->getPost('token')));
}
public function aktifitas(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->aktifitas(
(string) $this->request->getPost('token'),
$this->request->getPost('dari'),
$this->request->getPost('jumlah'),
));
}
public function save_pp(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->savePp(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('nama_photo'),
(string) $this->request->getPost('photo'),
));
}
public function save_password(): ResponseInterface
{
return $this->respondLegacy($this->mobileJson->savePassword(
(string) $this->request->getPost('token'),
(string) $this->request->getPost('pass_lama'),
(string) $this->request->getPost('pass_baru'),
));
}
}