init backend presensi

This commit is contained in:
mwpn
2026-03-05 14:37:36 +07:00
commit b4fda6b9c9
319 changed files with 27261 additions and 0 deletions

View File

@@ -0,0 +1 @@
# Devices Module - Controllers

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Modules\Devices\Controllers;
use App\Core\BaseApiController;
use App\Modules\Devices\Services\DeviceAuthService;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Device Authentication Controller
*
* Handles device authentication endpoints.
*/
class DeviceAuthController extends BaseApiController
{
protected DeviceAuthService $deviceAuthService;
public function __construct()
{
$this->deviceAuthService = new DeviceAuthService();
}
/**
* Device login endpoint
*
* Authenticates device using device_code and api_key.
*
* POST /api/device/login
* Body: { "device_code": "", "api_key": "" }
*
* @return ResponseInterface
*/
public function login(): ResponseInterface
{
// Get JSON input
$input = $this->request->getJSON(true);
// Validate input
if (empty($input['device_code']) || empty($input['api_key'])) {
return $this->errorResponse(
'device_code and api_key are required',
null,
null,
400
);
}
// Authenticate device
$deviceData = $this->deviceAuthService->authenticate(
$input['device_code'],
$input['api_key']
);
if (!$deviceData) {
return $this->errorResponse(
'Invalid device credentials',
null,
null,
401
);
}
// Return success response
return $this->successResponse(
[
'device_id' => $deviceData['device_id'],
'device_code' => $deviceData['device_code'],
],
'Device authenticated'
);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Modules\Devices\Controllers;
use App\Core\BaseApiController;
use App\Modules\Devices\Models\DeviceModel;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Device Controller
*
* Admin-only management for device configuration (geo-fence, etc).
*/
class DeviceController extends BaseApiController
{
/**
* PUT /api/devices/{id}
*
* Body (JSON):
* - latitude: float|null
* - longitude: float|null
* - radius_meters: int|null
*/
public function update($id): ResponseInterface
{
$id = (int) $id;
if ($id <= 0) {
return $this->errorResponse('Invalid device id', null, null, ResponseInterface::HTTP_BAD_REQUEST);
}
$payload = $this->request->getJSON(true) ?? [];
$lat = $payload['latitude'] ?? null;
$lng = $payload['longitude'] ?? null;
$radius = $payload['radius_meters'] ?? null;
// Normalize empty strings to null
$lat = ($lat === '' || $lat === null) ? null : $lat;
$lng = ($lng === '' || $lng === null) ? null : $lng;
$radius = ($radius === '' || $radius === null) ? null : $radius;
// Basic validation
if ($lat !== null && !is_numeric($lat)) {
return $this->errorResponse('Latitude harus berupa angka atau kosong', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
if ($lng !== null && !is_numeric($lng)) {
return $this->errorResponse('Longitude harus berupa angka atau kosong', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
if ($radius !== null && (!is_numeric($radius) || (int) $radius < 0)) {
return $this->errorResponse('Radius harus berupa angka >= 0 atau kosong', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
// If one of lat/lng/radius set, require all three
$hasAny = $lat !== null || $lng !== null || $radius !== null;
if ($hasAny) {
if ($lat === null || $lng === null || $radius === null) {
return $this->errorResponse('Jika mengatur zona, latitude, longitude, dan radius wajib diisi semua', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
}
$data = [
'latitude' => $lat !== null ? (float) $lat : null,
'longitude' => $lng !== null ? (float) $lng : null,
'radius_meters' => $radius !== null ? (int) $radius : null,
];
$model = new DeviceModel();
$device = $model->find($id);
if (!$device) {
return $this->errorResponse('Device tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
}
if (!$model->update($id, $data)) {
return $this->errorResponse('Gagal menyimpan konfigurasi device', $model->errors(), null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
return $this->successResponse(null, 'Konfigurasi geo-fence device berhasil disimpan');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Modules\Devices\Controllers;
use App\Core\BaseApiController;
use App\Modules\Geo\Models\ZoneModel;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Mobile Support Controller
*
* Endpoints for Android app (ping, bootstrap). No authentication required.
*/
class MobileController extends BaseApiController
{
/**
* Default check-in late tolerance in minutes (must match AttendanceCheckinService)
*/
protected int $checkinToleranceMinutes = 10;
/**
* GET /api/mobile/ping
*
* @return ResponseInterface
*/
public function ping(): ResponseInterface
{
$data = [
'server_time' => date('Y-m-d H:i:s'),
'api_version' => '1.0',
];
return $this->successResponse($data, 'Mobile connected');
}
/**
* GET /api/mobile/bootstrap
*
* Returns active zones, device validation rules, and checkin tolerance for app startup.
*
* @return ResponseInterface
*/
public function bootstrap(): ResponseInterface
{
$zoneModel = new ZoneModel();
$activeZones = $zoneModel->findAllActive();
$zonesData = [];
foreach ($activeZones as $zone) {
$zonesData[] = [
'zone_code' => $zone->zone_code,
'zone_name' => $zone->zone_name,
'latitude' => (float) $zone->latitude,
'longitude' => (float) $zone->longitude,
'radius_meters' => (int) $zone->radius_meters,
];
}
$data = [
'server_timestamp' => gmdate('Y-m-d\TH:i:s\Z'), // ISO 8601 UTC — for device_time_offset = server_time - device_time
'server_timezone' => 'Asia/Jakarta', // WIB — for display / jadwal sekolah
'active_zones' => $zonesData,
'device_validation_rules' => [
'require_device_code' => true,
'require_api_key' => true,
],
'checkin_tolerance_minutes' => $this->checkinToleranceMinutes,
];
return $this->successResponse($data, 'Bootstrap data');
}
}