feat: tambah endpoint frontend dan field camera di gate
- Tambah endpoint GET /tariffs (list tariffs)
- Tambah endpoint GET /locations/{code} (detail location)
- Tambah endpoint GET /gates/{location_code}/{gate_code} (detail gate)
- Tambah endpoint GET /tariffs/{location_code}/{gate_code}/{category} (detail tariff)
- Tambah endpoint GET /audit-logs (audit trail history)
- Tambah endpoint GET /entry-events (raw entry events)
- Tambah endpoint GET /realtime/events (realtime events list)
- Tambah field camera di gates (support HLS, RTSP, HTTP streaming)
- Migration 004: add camera column to gates table
- Update validasi dan service untuk support camera field
This commit is contained in:
98
src/Modules/Retribusi/Frontend/AuditController.php
Normal file
98
src/Modules/Retribusi/Frontend/AuditController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Retribusi\Frontend;
|
||||
|
||||
use App\Support\ResponseHelper;
|
||||
use App\Support\Validator;
|
||||
use PDOException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class AuditController
|
||||
{
|
||||
private AuditService $auditService;
|
||||
|
||||
public function __construct(AuditService $auditService)
|
||||
{
|
||||
$this->auditService = $auditService;
|
||||
}
|
||||
|
||||
public function getAuditLogs(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): ResponseInterface {
|
||||
$queryParams = $request->getQueryParams();
|
||||
[$page, $limit] = Validator::validatePagination($queryParams);
|
||||
|
||||
$entity = $queryParams['entity'] ?? null;
|
||||
if ($entity !== null && !is_string($entity)) {
|
||||
$entity = null;
|
||||
}
|
||||
|
||||
$action = $queryParams['action'] ?? null;
|
||||
if ($action !== null && !is_string($action)) {
|
||||
$action = null;
|
||||
}
|
||||
|
||||
$entityKey = $queryParams['entity_key'] ?? null;
|
||||
if ($entityKey !== null && !is_string($entityKey)) {
|
||||
$entityKey = null;
|
||||
}
|
||||
|
||||
$startDate = $queryParams['start_date'] ?? null;
|
||||
if ($startDate !== null && !is_string($startDate)) {
|
||||
$startDate = null;
|
||||
}
|
||||
|
||||
$endDate = $queryParams['end_date'] ?? null;
|
||||
if ($endDate !== null && !is_string($endDate)) {
|
||||
$endDate = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->auditService->getAuditLogs(
|
||||
$page,
|
||||
$limit,
|
||||
$entity,
|
||||
$action,
|
||||
$entityKey,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
$total = $this->auditService->getAuditLogsTotal(
|
||||
$entity,
|
||||
$action,
|
||||
$entityKey,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => (int) ceil($total / $limit)
|
||||
],
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'server_error',
|
||||
'message' => 'Database error occurred'
|
||||
],
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,149 @@ class AuditService
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get audit logs with pagination and optional filters
|
||||
*
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @param string|null $entity Optional filter by entity (locations|gates|tariffs)
|
||||
* @param string|null $action Optional filter by action (create|update|delete)
|
||||
* @param string|null $entityKey Optional filter by entity key
|
||||
* @param string|null $startDate Optional start date (YYYY-MM-DD)
|
||||
* @param string|null $endDate Optional end date (YYYY-MM-DD)
|
||||
* @return array
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getAuditLogs(
|
||||
int $page,
|
||||
int $limit,
|
||||
?string $entity = null,
|
||||
?string $action = null,
|
||||
?string $entityKey = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): array {
|
||||
$offset = ($page - 1) * $limit;
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($entity !== null) {
|
||||
$where[] = 'entity = ?';
|
||||
$params[] = $entity;
|
||||
}
|
||||
|
||||
if ($action !== null) {
|
||||
$where[] = 'action = ?';
|
||||
$params[] = $action;
|
||||
}
|
||||
|
||||
if ($entityKey !== null) {
|
||||
$where[] = 'entity_key = ?';
|
||||
$params[] = $entityKey;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(created_at) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(created_at) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT id, actor_user_id, actor_username, actor_role, action, entity, entity_key,
|
||||
before_json, after_json, ip_address, user_agent, created_at
|
||||
FROM audit_logs
|
||||
{$whereClause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$paramIndex = 1;
|
||||
foreach ($params as $param) {
|
||||
$stmt->bindValue($paramIndex++, $param, PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->bindValue($paramIndex++, $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue($paramIndex, $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$results = $stmt->fetchAll();
|
||||
|
||||
// Parse JSON fields
|
||||
foreach ($results as &$result) {
|
||||
if ($result['before_json'] !== null) {
|
||||
$result['before_json'] = json_decode($result['before_json'], true);
|
||||
}
|
||||
if ($result['after_json'] !== null) {
|
||||
$result['after_json'] = json_decode($result['after_json'], true);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of audit logs
|
||||
*
|
||||
* @param string|null $entity Optional filter by entity
|
||||
* @param string|null $action Optional filter by action
|
||||
* @param string|null $entityKey Optional filter by entity key
|
||||
* @param string|null $startDate Optional start date
|
||||
* @param string|null $endDate Optional end date
|
||||
* @return int
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getAuditLogsTotal(
|
||||
?string $entity = null,
|
||||
?string $action = null,
|
||||
?string $entityKey = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): int {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($entity !== null) {
|
||||
$where[] = 'entity = ?';
|
||||
$params[] = $entity;
|
||||
}
|
||||
|
||||
if ($action !== null) {
|
||||
$where[] = 'action = ?';
|
||||
$params[] = $action;
|
||||
}
|
||||
|
||||
if ($entityKey !== null) {
|
||||
$where[] = 'entity_key = ?';
|
||||
$params[] = $entityKey;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(created_at) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(created_at) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT COUNT(*) FROM audit_logs {$whereClause}";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
$stmt = $this->db->query($sql);
|
||||
}
|
||||
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client IP address from request
|
||||
*
|
||||
|
||||
99
src/Modules/Retribusi/Frontend/EntryEventsController.php
Normal file
99
src/Modules/Retribusi/Frontend/EntryEventsController.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Retribusi\Frontend;
|
||||
|
||||
use App\Modules\Retribusi\Realtime\RealtimeService;
|
||||
use App\Support\ResponseHelper;
|
||||
use App\Support\Validator;
|
||||
use PDOException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class EntryEventsController
|
||||
{
|
||||
private RealtimeService $realtimeService;
|
||||
|
||||
public function __construct(RealtimeService $realtimeService)
|
||||
{
|
||||
$this->realtimeService = $realtimeService;
|
||||
}
|
||||
|
||||
public function getEntryEvents(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): ResponseInterface {
|
||||
$queryParams = $request->getQueryParams();
|
||||
[$page, $limit] = Validator::validatePagination($queryParams);
|
||||
|
||||
$locationCode = $queryParams['location_code'] ?? null;
|
||||
if ($locationCode !== null && !is_string($locationCode)) {
|
||||
$locationCode = null;
|
||||
}
|
||||
|
||||
$gateCode = $queryParams['gate_code'] ?? null;
|
||||
if ($gateCode !== null && !is_string($gateCode)) {
|
||||
$gateCode = null;
|
||||
}
|
||||
|
||||
$category = $queryParams['category'] ?? null;
|
||||
if ($category !== null && !is_string($category)) {
|
||||
$category = null;
|
||||
}
|
||||
|
||||
$startDate = $queryParams['start_date'] ?? null;
|
||||
if ($startDate !== null && !is_string($startDate)) {
|
||||
$startDate = null;
|
||||
}
|
||||
|
||||
$endDate = $queryParams['end_date'] ?? null;
|
||||
if ($endDate !== null && !is_string($endDate)) {
|
||||
$endDate = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->realtimeService->getEntryEvents(
|
||||
$page,
|
||||
$limit,
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
$total = $this->realtimeService->getEntryEventsTotal(
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => (int) ceil($total / $limit)
|
||||
],
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'server_error',
|
||||
'message' => 'Database error occurred'
|
||||
],
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,48 @@ class GateController
|
||||
);
|
||||
}
|
||||
|
||||
public function getGate(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $args
|
||||
): ResponseInterface {
|
||||
$locationCode = $args['location_code'] ?? null;
|
||||
$gateCode = $args['gate_code'] ?? null;
|
||||
|
||||
if ($locationCode === null || !is_string($locationCode) ||
|
||||
$gateCode === null || !is_string($gateCode)) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'validation_error',
|
||||
'fields' => ['location_code' => 'Invalid location_code or gate_code']
|
||||
],
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
$data = $this->writeService->getGate($locationCode, $gateCode);
|
||||
if ($data === null) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'not_found',
|
||||
'message' => 'Gate not found'
|
||||
],
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function createGate(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
|
||||
@@ -52,6 +52,45 @@ class LocationController
|
||||
);
|
||||
}
|
||||
|
||||
public function getLocation(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $args
|
||||
): ResponseInterface {
|
||||
$code = $args['code'] ?? null;
|
||||
if ($code === null || !is_string($code)) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'validation_error',
|
||||
'fields' => ['code' => 'Invalid location code']
|
||||
],
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
$data = $this->writeService->getLocation($code);
|
||||
if ($data === null) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'not_found',
|
||||
'message' => 'Location not found'
|
||||
],
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function createLocation(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
|
||||
@@ -69,7 +69,7 @@ class RetribusiReadService
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT g.location_code, g.gate_code, g.name, g.direction, g.is_active,
|
||||
'SELECT g.location_code, g.gate_code, g.name, g.direction, g.camera, g.is_active,
|
||||
l.name as location_name
|
||||
FROM gates g
|
||||
INNER JOIN locations l ON g.location_code = l.code
|
||||
@@ -83,7 +83,7 @@ class RetribusiReadService
|
||||
$stmt->execute();
|
||||
} else {
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT g.location_code, g.gate_code, g.name, g.direction, g.is_active,
|
||||
'SELECT g.location_code, g.gate_code, g.name, g.direction, g.camera, g.is_active,
|
||||
l.name as location_name
|
||||
FROM gates g
|
||||
INNER JOIN locations l ON g.location_code = l.code
|
||||
@@ -142,4 +142,90 @@ class RetribusiReadService
|
||||
{
|
||||
return $this->getGatesTotal($locationCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tariffs list with pagination and optional filters
|
||||
*
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @return array
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getTariffs(int $page, int $limit, ?string $locationCode = null, ?string $gateCode = null): array
|
||||
{
|
||||
$offset = ($page - 1) * $limit;
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 't.location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 't.gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT t.location_code, t.gate_code, t.category, t.price,
|
||||
l.name as location_name,
|
||||
g.name as gate_name
|
||||
FROM tariffs t
|
||||
INNER JOIN locations l ON t.location_code = l.code
|
||||
INNER JOIN gates g ON t.location_code = g.location_code AND t.gate_code = g.gate_code
|
||||
{$whereClause}
|
||||
ORDER BY t.location_code, t.gate_code, t.category ASC
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$paramIndex = 1;
|
||||
foreach ($params as $param) {
|
||||
$stmt->bindValue($paramIndex++, $param, PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->bindValue($paramIndex++, $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue($paramIndex, $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of tariffs
|
||||
*
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @return int
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getTariffsTotal(?string $locationCode = null, ?string $gateCode = null): int
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 'location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 'gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT COUNT(*) FROM tariffs {$whereClause}";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
$stmt = $this->db->query($sql);
|
||||
}
|
||||
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class RetribusiWriteService
|
||||
public function getGate(string $locationCode, string $gateCode): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT location_code, gate_code, name, direction, is_active
|
||||
'SELECT location_code, gate_code, name, direction, camera, is_active
|
||||
FROM gates
|
||||
WHERE location_code = ? AND gate_code = ?
|
||||
LIMIT 1'
|
||||
@@ -147,10 +147,11 @@ class RetribusiWriteService
|
||||
public function createGate(array $data): array
|
||||
{
|
||||
$direction = isset($data['direction']) ? strtolower($data['direction']) : $data['direction'];
|
||||
$camera = $data['camera'] ?? null;
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO gates (location_code, gate_code, name, direction, is_active)
|
||||
VALUES (?, ?, ?, ?, ?)'
|
||||
'INSERT INTO gates (location_code, gate_code, name, direction, camera, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
|
||||
$stmt->execute([
|
||||
@@ -158,6 +159,7 @@ class RetribusiWriteService
|
||||
$data['gate_code'],
|
||||
$data['name'],
|
||||
$direction,
|
||||
$camera,
|
||||
$data['is_active']
|
||||
]);
|
||||
|
||||
@@ -188,6 +190,11 @@ class RetribusiWriteService
|
||||
$params[] = strtolower($data['direction']);
|
||||
}
|
||||
|
||||
if (isset($data['camera'])) {
|
||||
$updates[] = 'camera = ?';
|
||||
$params[] = $data['camera'];
|
||||
}
|
||||
|
||||
if (isset($data['is_active'])) {
|
||||
$updates[] = 'is_active = ?';
|
||||
$params[] = $data['is_active'];
|
||||
|
||||
@@ -12,17 +12,100 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class TariffController
|
||||
{
|
||||
private RetribusiReadService $readService;
|
||||
private RetribusiWriteService $writeService;
|
||||
private AuditService $auditService;
|
||||
|
||||
public function __construct(
|
||||
RetribusiReadService $readService,
|
||||
RetribusiWriteService $writeService,
|
||||
AuditService $auditService
|
||||
) {
|
||||
$this->readService = $readService;
|
||||
$this->writeService = $writeService;
|
||||
$this->auditService = $auditService;
|
||||
}
|
||||
|
||||
public function getTariffs(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): ResponseInterface {
|
||||
$queryParams = $request->getQueryParams();
|
||||
[$page, $limit] = Validator::validatePagination($queryParams);
|
||||
|
||||
$locationCode = $queryParams['location_code'] ?? null;
|
||||
if ($locationCode !== null && !is_string($locationCode)) {
|
||||
$locationCode = null;
|
||||
}
|
||||
|
||||
$gateCode = $queryParams['gate_code'] ?? null;
|
||||
if ($gateCode !== null && !is_string($gateCode)) {
|
||||
$gateCode = null;
|
||||
}
|
||||
|
||||
$data = $this->readService->getTariffs($page, $limit, $locationCode, $gateCode);
|
||||
$total = $this->readService->getTariffsTotal($locationCode, $gateCode);
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => (int) ceil($total / $limit)
|
||||
],
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getTariff(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $args
|
||||
): ResponseInterface {
|
||||
$locationCode = $args['location_code'] ?? null;
|
||||
$gateCode = $args['gate_code'] ?? null;
|
||||
$category = $args['category'] ?? null;
|
||||
|
||||
if ($locationCode === null || !is_string($locationCode) ||
|
||||
$gateCode === null || !is_string($gateCode) ||
|
||||
$category === null || !is_string($category)) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'validation_error',
|
||||
'fields' => ['location_code' => 'Invalid location_code, gate_code, or category']
|
||||
],
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
$data = $this->writeService->getTariff($locationCode, $gateCode, $category);
|
||||
if ($data === null) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'not_found',
|
||||
'message' => 'Tariff not found'
|
||||
],
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function createTariff(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Retribusi\Realtime;
|
||||
|
||||
use App\Support\ResponseHelper;
|
||||
use App\Support\Validator;
|
||||
use PDOException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -200,5 +202,171 @@ class RealtimeController
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry events list
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getEntryEvents(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): ResponseInterface {
|
||||
$queryParams = $request->getQueryParams();
|
||||
[$page, $limit] = Validator::validatePagination($queryParams);
|
||||
|
||||
$locationCode = $queryParams['location_code'] ?? null;
|
||||
if ($locationCode !== null && !is_string($locationCode)) {
|
||||
$locationCode = null;
|
||||
}
|
||||
|
||||
$gateCode = $queryParams['gate_code'] ?? null;
|
||||
if ($gateCode !== null && !is_string($gateCode)) {
|
||||
$gateCode = null;
|
||||
}
|
||||
|
||||
$category = $queryParams['category'] ?? null;
|
||||
if ($category !== null && !is_string($category)) {
|
||||
$category = null;
|
||||
}
|
||||
|
||||
$startDate = $queryParams['start_date'] ?? null;
|
||||
if ($startDate !== null && !is_string($startDate)) {
|
||||
$startDate = null;
|
||||
}
|
||||
|
||||
$endDate = $queryParams['end_date'] ?? null;
|
||||
if ($endDate !== null && !is_string($endDate)) {
|
||||
$endDate = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->service->getEntryEvents(
|
||||
$page,
|
||||
$limit,
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
$total = $this->service->getEntryEventsTotal(
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => (int) ceil($total / $limit)
|
||||
],
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'server_error',
|
||||
'message' => 'Database error occurred'
|
||||
],
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realtime events list
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRealtimeEvents(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): ResponseInterface {
|
||||
$queryParams = $request->getQueryParams();
|
||||
[$page, $limit] = Validator::validatePagination($queryParams);
|
||||
|
||||
$locationCode = $queryParams['location_code'] ?? null;
|
||||
if ($locationCode !== null && !is_string($locationCode)) {
|
||||
$locationCode = null;
|
||||
}
|
||||
|
||||
$gateCode = $queryParams['gate_code'] ?? null;
|
||||
if ($gateCode !== null && !is_string($gateCode)) {
|
||||
$gateCode = null;
|
||||
}
|
||||
|
||||
$category = $queryParams['category'] ?? null;
|
||||
if ($category !== null && !is_string($category)) {
|
||||
$category = null;
|
||||
}
|
||||
|
||||
$startDate = $queryParams['start_date'] ?? null;
|
||||
if ($startDate !== null && !is_string($startDate)) {
|
||||
$startDate = null;
|
||||
}
|
||||
|
||||
$endDate = $queryParams['end_date'] ?? null;
|
||||
if ($endDate !== null && !is_string($endDate)) {
|
||||
$endDate = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->service->getRealtimeEvents(
|
||||
$page,
|
||||
$limit,
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
$total = $this->service->getRealtimeEventsTotal(
|
||||
$locationCode,
|
||||
$gateCode,
|
||||
$category,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'meta' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => (int) ceil($total / $limit)
|
||||
],
|
||||
'timestamp' => time()
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
return ResponseHelper::json(
|
||||
$response,
|
||||
[
|
||||
'error' => 'server_error',
|
||||
'message' => 'Database error occurred'
|
||||
],
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ class RealtimeRoutes
|
||||
$v1Group->group('/realtime', function ($realtimeGroup) use ($realtimeController) {
|
||||
$realtimeGroup->get('/stream', [$realtimeController, 'stream']);
|
||||
$realtimeGroup->get('/snapshot', [$realtimeController, 'getSnapshot']);
|
||||
$realtimeGroup->get('/events', [$realtimeController, 'getRealtimeEvents']);
|
||||
})->add($jwtMiddleware);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,5 +162,265 @@ class RealtimeService
|
||||
'by_category' => $byCategoryFormatted
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry events list with pagination and optional filters
|
||||
*
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @param string|null $category Optional filter by category
|
||||
* @param string|null $startDate Optional start date (YYYY-MM-DD)
|
||||
* @param string|null $endDate Optional end date (YYYY-MM-DD)
|
||||
* @return array
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getEntryEvents(
|
||||
int $page,
|
||||
int $limit,
|
||||
?string $locationCode = null,
|
||||
?string $gateCode = null,
|
||||
?string $category = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): array {
|
||||
$offset = ($page - 1) * $limit;
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 'location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 'gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$where[] = 'category = ?';
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(event_time) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(event_time) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT id, location_code, gate_code, category, event_time, source_ip, created_at
|
||||
FROM entry_events
|
||||
{$whereClause}
|
||||
ORDER BY event_time DESC, id DESC
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$paramIndex = 1;
|
||||
foreach ($params as $param) {
|
||||
$stmt->bindValue($paramIndex++, $param, PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->bindValue($paramIndex++, $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue($paramIndex, $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of entry events
|
||||
*
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @param string|null $category Optional filter by category
|
||||
* @param string|null $startDate Optional start date
|
||||
* @param string|null $endDate Optional end date
|
||||
* @return int
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getEntryEventsTotal(
|
||||
?string $locationCode = null,
|
||||
?string $gateCode = null,
|
||||
?string $category = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): int {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 'location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 'gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$where[] = 'category = ?';
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(event_time) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(event_time) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT COUNT(*) FROM entry_events {$whereClause}";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
$stmt = $this->db->query($sql);
|
||||
}
|
||||
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realtime events list with pagination and optional filters
|
||||
*
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @param string|null $category Optional filter by category
|
||||
* @param string|null $startDate Optional start date (YYYY-MM-DD)
|
||||
* @param string|null $endDate Optional end date (YYYY-MM-DD)
|
||||
* @return array
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getRealtimeEvents(
|
||||
int $page,
|
||||
int $limit,
|
||||
?string $locationCode = null,
|
||||
?string $gateCode = null,
|
||||
?string $category = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): array {
|
||||
$offset = ($page - 1) * $limit;
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 'location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 'gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$where[] = 'category = ?';
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(created_at) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(created_at) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT id, location_code, gate_code, category, event_time, total_count_delta, created_at
|
||||
FROM realtime_events
|
||||
{$whereClause}
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$paramIndex = 1;
|
||||
foreach ($params as $param) {
|
||||
$stmt->bindValue($paramIndex++, $param, PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->bindValue($paramIndex++, $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue($paramIndex, $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of realtime events
|
||||
*
|
||||
* @param string|null $locationCode Optional filter by location
|
||||
* @param string|null $gateCode Optional filter by gate
|
||||
* @param string|null $category Optional filter by category
|
||||
* @param string|null $startDate Optional start date
|
||||
* @param string|null $endDate Optional end date
|
||||
* @return int
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function getRealtimeEventsTotal(
|
||||
?string $locationCode = null,
|
||||
?string $gateCode = null,
|
||||
?string $category = null,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null
|
||||
): int {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($locationCode !== null) {
|
||||
$where[] = 'location_code = ?';
|
||||
$params[] = $locationCode;
|
||||
}
|
||||
|
||||
if ($gateCode !== null) {
|
||||
$where[] = 'gate_code = ?';
|
||||
$params[] = $gateCode;
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$where[] = 'category = ?';
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($startDate !== null) {
|
||||
$where[] = 'DATE(created_at) >= ?';
|
||||
$params[] = $startDate;
|
||||
}
|
||||
|
||||
if ($endDate !== null) {
|
||||
$where[] = 'DATE(created_at) <= ?';
|
||||
$params[] = $endDate;
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT COUNT(*) FROM realtime_events {$whereClause}";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
$stmt = $this->db->query($sql);
|
||||
}
|
||||
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ use App\Config\AppConfig;
|
||||
use App\Middleware\ApiKeyMiddleware;
|
||||
use App\Middleware\JwtMiddleware;
|
||||
use App\Middleware\RoleMiddleware;
|
||||
use App\Modules\Retribusi\Frontend\AuditController;
|
||||
use App\Modules\Retribusi\Frontend\AuditService;
|
||||
use App\Modules\Retribusi\Frontend\EntryEventsController;
|
||||
use App\Modules\Retribusi\Frontend\GateController;
|
||||
use App\Modules\Retribusi\Frontend\LocationController;
|
||||
use App\Modules\Retribusi\Frontend\RetribusiReadService;
|
||||
use App\Modules\Retribusi\Frontend\RetribusiWriteService;
|
||||
use App\Modules\Retribusi\Frontend\StreamController;
|
||||
use App\Modules\Retribusi\Frontend\TariffController;
|
||||
use App\Modules\Retribusi\Realtime\RealtimeService;
|
||||
use App\Modules\Retribusi\Ingest\IngestController;
|
||||
use App\Modules\Retribusi\Ingest\IngestService;
|
||||
use App\Support\Database;
|
||||
@@ -62,7 +65,11 @@ class RetribusiRoutes
|
||||
$gateController = new GateController($readService, $writeService, $auditService);
|
||||
$locationController = new LocationController($readService, $writeService, $auditService);
|
||||
$streamController = new StreamController($readService);
|
||||
$tariffController = new TariffController($writeService, $auditService);
|
||||
$tariffController = new TariffController($readService, $writeService, $auditService);
|
||||
$auditController = new AuditController($auditService);
|
||||
|
||||
$realtimeService = new RealtimeService($db);
|
||||
$entryEventsController = new EntryEventsController($realtimeService);
|
||||
|
||||
// Register routes
|
||||
$app->group('/retribusi', function ($group) use (
|
||||
@@ -74,7 +81,9 @@ class RetribusiRoutes
|
||||
$gateController,
|
||||
$locationController,
|
||||
$streamController,
|
||||
$tariffController
|
||||
$tariffController,
|
||||
$auditController,
|
||||
$entryEventsController
|
||||
) {
|
||||
$group->group('/v1', function ($v1Group) use (
|
||||
$apiKeyMiddleware,
|
||||
@@ -85,7 +94,9 @@ class RetribusiRoutes
|
||||
$gateController,
|
||||
$locationController,
|
||||
$streamController,
|
||||
$tariffController
|
||||
$tariffController,
|
||||
$auditController,
|
||||
$entryEventsController
|
||||
) {
|
||||
// Ingest routes (with API key middleware)
|
||||
$v1Group->post('/ingest', [$ingestController, 'ingest'])
|
||||
@@ -98,12 +109,20 @@ class RetribusiRoutes
|
||||
$gateController,
|
||||
$locationController,
|
||||
$streamController,
|
||||
$tariffController
|
||||
$tariffController,
|
||||
$auditController,
|
||||
$entryEventsController
|
||||
) {
|
||||
// Read routes (viewer, operator, admin)
|
||||
$frontendGroup->get('/gates', [$gateController, 'getGates']);
|
||||
$frontendGroup->get('/gates/{location_code}/{gate_code}', [$gateController, 'getGate']);
|
||||
$frontendGroup->get('/locations', [$locationController, 'getLocations']);
|
||||
$frontendGroup->get('/locations/{code}', [$locationController, 'getLocation']);
|
||||
$frontendGroup->get('/streams', [$streamController, 'getStreams']);
|
||||
$frontendGroup->get('/tariffs', [$tariffController, 'getTariffs']);
|
||||
$frontendGroup->get('/tariffs/{location_code}/{gate_code}/{category}', [$tariffController, 'getTariff']);
|
||||
$frontendGroup->get('/audit-logs', [$auditController, 'getAuditLogs']);
|
||||
$frontendGroup->get('/entry-events', [$entryEventsController, 'getEntryEvents']);
|
||||
|
||||
// Write routes (operator, admin)
|
||||
$frontendGroup->post('/locations', [$locationController, 'createLocation'])
|
||||
|
||||
Reference in New Issue
Block a user