TKK_E32230908/routes/api.php

374 lines
11 KiB
PHP

<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;
use App\Models\BorrowTransaction;
use App\Models\IotMode;
use PhpMqtt\Client\MqttClient;
use App\Http\Controllers\Admin\PeminjamanController;
use App\Http\Controllers\User\UserPeminjamanController;
use App\Jobs\ProcessPeminjamanJob;
use App\Jobs\ProcessPengembalianJob;
/*
|--------------------------------------------------------------------------
| API Routes - Nawasena IoT Library System (Synchronized)
|--------------------------------------------------------------------------
*/
/* ================= HELPERS ================= */
if (!function_exists('cleanData')) {
function cleanData($data) {
return strtoupper(str_replace(' ', '', trim($data)));
}
}
if (!function_exists('mqttPublish')) {
function mqttPublish($topic, $message) {
try {
// Menggunakan Unique Client ID untuk menghindari konflik koneksi
$mqtt = new MqttClient('127.0.0.1', 1883, 'laravel-api-pub-' . uniqid());
$mqtt->connect(null, false);
$payload = is_array($message) ? json_encode($message) : (string)$message;
$mqtt->publish($topic, $payload, 0);
$mqtt->disconnect();
} catch (\Exception $e) {
\Log::error("MQTT Error: " . $e->getMessage());
}
}
}
/* ================= 1. VERIFIKASI RFID (IDENTIFIKASI SEMENTARA) ================= */
Route::get('/esp/check-member', function (Request $request) {
$uid = cleanData($request->query('uid'));
if (!$uid) return response()->json(['status' => 'error', 'message' => 'UID Kosong'], 400);
$iot = IotMode::find(1);
// Tambahan: Cegah proses jika sistem sedang dalam state lock 'failed'
if ($iot && $iot->mode === 'wait') {
return response()->json(['status' => 'error', 'message' => 'Sistem terkunci sementara'], 423);
}
$anggota = DB::table('anggota')->where('uid', $uid)->orWhere('uid_clean', $uid)->first();
if (!$anggota) {
// Jika kartu salah, ubah mode jadi failed untuk mengunci scan ganda
if ($iot) $iot->update(['mode' => 'wait']);
mqttPublish('rfid/status', 'failed');
mqttPublish('rfid/ui/member', ['status' => 'invalid']);
return response()->json(['status' => 'gagal', 'message' => "Kartu $uid tidak terdaftar"], 404);
}
if ($iot && $iot->book_id && ($iot->mode === 'wait' || $iot->mode === 'identified')) {
$iot->update([
'mode' => 'identified',
'user_id' => $anggota->id
]);
mqttPublish('rfid/status', 'valid');
mqttPublish('rfid/ui/member', [
'status' => 'valid',
'nama' => $anggota->nama,
'uid' => $uid
]);
return response()->json([
'status' => 'berhasil',
'id' => $anggota->id,
'nama' => $anggota->nama,
'message'=> 'Anggota teridentifikasi, silakan klik lanjutkan di layar.'
], 200);
}
return response()->json(['status' => 'error', 'message' => 'Sistem tidak siap'], 400);
});
/* ================= 2. KONFIRMASI PEMINJAMAN REAL ================= */
Route::get('/esp/konfirmasi-pinjam', function () {
$iot = IotMode::find(1);
/*
|--------------------------------------------------------------------------
| Validasi State
|--------------------------------------------------------------------------
*/
if (
!$iot ||
!$iot->book_id ||
!$iot->user_id
) {
return response()->json([
'status' => 'error',
'message' => 'Data tidak lengkap'
], 400);
}
/*
|--------------------------------------------------------------------------
| Pastikan Mode Sudah Identified
|--------------------------------------------------------------------------
*/
if ($iot->mode !== 'identified') {
return response()->json([
'status' => 'error',
'message' => 'Sistem belum siap'
], 400);
}
try {
/*
|--------------------------------------------------------------------------
| Lock State
|--------------------------------------------------------------------------
*/
$iot->update([
'mode' => 'processing'
]);
/*
|--------------------------------------------------------------------------
| MQTT Feedback ke ESP32
|--------------------------------------------------------------------------
*/
mqttPublish('rfid/status', 'processing');
/*
|--------------------------------------------------------------------------
| Dispatch Queue
|--------------------------------------------------------------------------
*/
ProcessPeminjamanJob::dispatch(
$iot->book_id,
$iot->user_id
);
/*
|--------------------------------------------------------------------------
| Response Cepat ke Frontend
|--------------------------------------------------------------------------
*/
return response()->json([
'status' => 'queued',
'message' => 'Peminjaman masuk queue'
]);
} catch (\Throwable $e) {
/*
|--------------------------------------------------------------------------
| Rollback State
|--------------------------------------------------------------------------
*/
$iot->update([
'mode' => 'wait'
]);
mqttPublish('rfid/status', 'failed');
\Log::error(
'Dispatch Queue Error: ' . $e->getMessage()
);
return response()->json([
'status' => 'error',
'message' => 'Queue gagal diproses'
], 500);
}
});
/* ================= 3. SCAN BUKU (PINJAM VS KEMBALI) ================= */
Route::post('/esp/scan-buku', function (Request $request) {
$barcode = cleanData($request->barcode);
if (!$barcode) return response()->json(['status' => 'barcode_kosong']);
$book = DB::table('koleksis')
->where('kode_eksemplar', $barcode)
->orWhere('barcode', $barcode)
->first();
if (!$book) return response()->json(['status' => 'tidak_ditemukan']);
$isBorrowed = DB::table('borrow_transactions')
->where('koleksi_id', $book->biblio_id)
->where('status', 'dipinjam')
->first();
if ($isBorrowed) {
$peminjam = DB::table('anggota')->where('id', $isBorrowed->anggota_id)->first();
IotMode::updateOrCreate(['id' => 1], [
'mode' => 'return_confirm',
'book_id' => $book->biblio_id,
'user_id' => $peminjam->id
]);
return response()->json([
'status' => 'konfirmasi_kembali',
'book' => [
'judul' => $book->judul_koleksi ?? $book->title,
'barcode' => $book->kode_eksemplar ?? $book->barcode,
'peminjam_saat_ini' => $peminjam ? $peminjam->nama : 'Anggota',
'tanggal_pinjam' => $isBorrowed->tanggal_pinjam
]
]);
}
IotMode::updateOrCreate(['id' => 1], [
'mode' => 'wait',
'book_id' => $book->biblio_id,
'user_id' => null
]);
mqttPublish('rfid/mode', 'wait');
return response()->json([
'status' => 'ok',
'book' => [
'judul' => $book->judul_koleksi ?? $book->title,
'barcode' => $book->kode_eksemplar ?? $book->barcode
]
]);
});
/* ================= 4. PROSES PENGEMBALIAN ================= */
Route::get('/esp/proses-kembali', function () {
$iot = IotMode::find(1);
if (
!$iot ||
$iot->mode !== 'return_confirm' ||
!$iot->book_id
) {
return response()->json([
'status' => 'error',
'message' => 'Sistem tidak dalam mode pengembalian'
], 400);
}
try {
// LOCK MODE
$iot->update([
'mode' => 'processing_return'
]);
// DISPATCH JOB KE QUEUE
ProcessPengembalianJob::dispatch([
'book_id' => $iot->book_id,
'user_id' => $iot->user_id
]);
// MQTT FEEDBACK CEPAT KE ESP32
mqttPublish('rfid/status', 'processing');
return response()->json([
'status' => 'queued',
'message' => 'Pengembalian diproses queue'
]);
} catch (\Throwable $e) {
logger()->error($e);
mqttPublish('rfid/status', 'error');
return response()->json([
'status' => 'error',
'message' => $e->getMessage()
], 500);
}
});
/* ================= 5. STATE, RESET, & BATAL ================= */
Route::get('/esp/state', function () {
$iot = IotMode::find(1);
if (!$iot) return response()->json(['status' => 'empty', 'mode' => 'standby']);
$book = $iot->book_id ? DB::table('koleksis')->where('biblio_id', $iot->book_id)->first() : null;
$user = $iot->user_id ? DB::table('anggota')->where('id', $iot->user_id)->first() : null;
return response()->json([
'status' => 'ok',
'mode' => $iot->mode,
'book' => $book ? [
'judul' => $book->judul_koleksi ?? $book->title,
'barcode' => $book->kode_eksemplar ?? $book->barcode
] : null,
'user' => $user ? [
'nama' => $user->nama
] : null
]);
});
Route::get('/esp/batalkan-transaksi', function () {
// Reset SQL ke mode standby
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
// Sinkronkan ke Hardware
mqttPublish('rfid/mode', 'standby');
mqttPublish('rfid/status', 'idle');
mqttPublish('rfid/cancel', 'reset'); // Signal tambahan jika Backbone MQTT memantau topik rfid/cancel
return response()->json(['status' => 'ok', 'message' => 'Transaksi dibatalkan']);
});
Route::get('/esp/selesai-transaksi', function() {
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
mqttPublish('rfid/mode', 'standby');
mqttPublish('rfid/status', 'idle');
return response()->json(['status' => 'ok']);
});
Route::get('/esp/reset', function () {
IotMode::where('id', 1)->update(['mode' => 'standby', 'book_id' => null, 'user_id' => null]);
mqttPublish('rfid/mode', 'standby');
mqttPublish('rfid/status', 'idle');
return response()->json(['status' => 'ok']);
});
/* ================= 6. ADMIN REGISTER MODE ================= */
Route::get('/esp/start-register/{user_id}', function ($user_id) {
try {
IotMode::updateOrCreate(['id' => 1], [
'mode' => 'admin',
'user_id' => $user_id,
'book_id' => null
]);
mqttPublish('rfid/mode', 'admin');
return response()->json([
'status' => 'ready',
'message' => 'Alat dalam mode pendaftaran. Silakan tap kartu.'
]);
} catch (\Exception $e) {
return response()->json(['status' => 'error'], 500);
}
});
/* ================= 7. CONTROLLER BINDINGS ================= */
Route::get('/esp/konfirmasi-kembali/{id}', [UserPeminjamanController::class, 'kembalikan']);
Route::get('/esp/konfirmasi-kembali', [PeminjamanController::class, 'konfirmasiKembali']);