init backend presensi
This commit is contained in:
187
app/Modules/Mobile/Controllers/CheckinController.php
Normal file
187
app/Modules/Mobile/Controllers/CheckinController.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Mobile\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Academic\Models\StudentModel;
|
||||
use App\Modules\Attendance\Services\AttendanceCheckinService;
|
||||
use App\Modules\Mobile\Models\StudentMobileAccountModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Check-in via QR (guru generate QR di dashboard, siswa scan di app).
|
||||
* POST /api/mobile/checkin-qr
|
||||
* Body: { nisn, pin, qr_token [, lat, lng ] }
|
||||
*
|
||||
* Absen masuk/pulang: POST /api/mobile/checkin-masuk-pulang
|
||||
* Body: { nisn, pin, type: 'masuk'|'pulang', lat, lng }
|
||||
*/
|
||||
class CheckinController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* Absen mapel via scan QR yang ditampilkan guru.
|
||||
*/
|
||||
public function checkinQr(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$nisn = trim((string) ($payload['nisn'] ?? ''));
|
||||
$pin = (string) ($payload['pin'] ?? '');
|
||||
$qrToken = trim((string) ($payload['qr_token'] ?? ''));
|
||||
|
||||
if ($nisn === '' || $pin === '' || $qrToken === '') {
|
||||
return $this->errorResponse('NISN, PIN, dan qr_token wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->findByNisn($nisn);
|
||||
if (!$student) {
|
||||
return $this->errorResponse('Siswa tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$accountRow = $accountModel->where('student_id', (int) $student->id)->first();
|
||||
if (!$accountRow || empty($accountRow['pin_hash']) || !password_verify($pin, $accountRow['pin_hash'])) {
|
||||
return $this->errorResponse('PIN salah', null, null, ResponseInterface::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$studentId = (int) $student->id;
|
||||
$checkinPayload = [
|
||||
'student_id' => $studentId,
|
||||
'qr_token' => $qrToken,
|
||||
'lat' => $payload['lat'] ?? 0,
|
||||
'lng' => $payload['lng'] ?? 0,
|
||||
];
|
||||
|
||||
$checkinService = new AttendanceCheckinService();
|
||||
$result = $checkinService->checkinByQr($checkinPayload);
|
||||
|
||||
$messages = [
|
||||
'PRESENT' => 'Absensi berhasil (Hadir)',
|
||||
'LATE' => 'Absensi berhasil (Terlambat)',
|
||||
'INVALID_QR_TOKEN' => 'QR tidak valid atau sudah kadaluarsa',
|
||||
'STUDENT_NOT_IN_CLASS' => 'Siswa tidak termasuk kelas untuk mapel ini',
|
||||
'ALREADY_CHECKED_IN' => 'Sudah absen untuk mapel ini hari ini',
|
||||
'NO_SCHEDULE' => 'Jadwal tidak ditemukan',
|
||||
'INVALID_DEVICE' => 'Kesalahan sistem',
|
||||
];
|
||||
$message = $messages[$result['status']] ?? $result['status'];
|
||||
|
||||
return $this->successResponse($result, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mobile/checkin-masuk-pulang
|
||||
* Body: { nisn, pin, type: 'masuk'|'pulang', lat, lng }
|
||||
* Absen masuk atau pulang pakai jam dari Pengaturan Presensi, auth NISN+PIN.
|
||||
*/
|
||||
public function checkinMasukPulang(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$nisn = trim((string) ($payload['nisn'] ?? ''));
|
||||
$pin = (string) ($payload['pin'] ?? '');
|
||||
$type = strtolower(trim((string) ($payload['type'] ?? '')));
|
||||
$lat = (float) ($payload['lat'] ?? 0);
|
||||
$lng = (float) ($payload['lng'] ?? 0);
|
||||
|
||||
if ($nisn === '' || $pin === '') {
|
||||
return $this->errorResponse('NISN dan PIN wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
if ($type !== 'masuk' && $type !== 'pulang') {
|
||||
return $this->errorResponse('type harus masuk atau pulang', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->findByNisn($nisn);
|
||||
if (! $student) {
|
||||
return $this->errorResponse('Siswa tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$accountRow = $accountModel->where('student_id', (int) $student->id)->first();
|
||||
if (! $accountRow || empty($accountRow['pin_hash']) || ! password_verify($pin, $accountRow['pin_hash'])) {
|
||||
return $this->errorResponse('PIN salah', null, null, ResponseInterface::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$checkinService = new AttendanceCheckinService();
|
||||
$result = $checkinService->checkinMasukPulang([
|
||||
'student_id' => (int) $student->id,
|
||||
'type' => $type,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
]);
|
||||
|
||||
$messages = [
|
||||
'PRESENT' => $type === 'masuk' ? 'Absen masuk berhasil.' : 'Absen pulang berhasil.',
|
||||
'LATE' => 'Absensi tercatat.',
|
||||
'OUTSIDE_ZONE' => 'Anda di luar jangkauan sekolah.',
|
||||
'NO_SCHEDULE' => 'Data tidak valid.',
|
||||
'INVALID_DEVICE' => 'Device aplikasi mobile belum dikonfigurasi. Hubungi admin untuk menambah device dengan kode MOBILE_APP.',
|
||||
'ALREADY_CHECKED_IN' => $type === 'masuk' ? 'Sudah absen masuk hari ini.' : 'Sudah absen pulang hari ini.',
|
||||
'ABSENCE_WINDOW_CLOSED' => 'Di luar jam absen. Cek jam masuk/pulang di pengaturan sekolah.',
|
||||
];
|
||||
$message = $messages[$result['status']] ?? $result['status'];
|
||||
|
||||
return $this->successResponse($result, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mobile/attendance/today?student_id=123
|
||||
* Returns today's attendance status for the student (untuk UI tombol Masuk/Pulang).
|
||||
*/
|
||||
public function todayStatus(): ResponseInterface
|
||||
{
|
||||
$studentId = (int) $this->request->getGet('student_id');
|
||||
if ($studentId < 1) {
|
||||
return $this->errorResponse('student_id wajib', null, null, ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$today = date('Y-m-d');
|
||||
$rows = $db->table('attendance_sessions')
|
||||
->select('checkin_at, checkin_type')
|
||||
->where('student_id', $studentId)
|
||||
->where('attendance_date', $today)
|
||||
->orderBy('checkin_at', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$has_masuk = false;
|
||||
$has_pulang = false;
|
||||
$first_at = null;
|
||||
$last_at = null;
|
||||
foreach ($rows as $r) {
|
||||
$t = $r['checkin_at'] ?? '';
|
||||
$ct = $r['checkin_type'] ?? 'mapel';
|
||||
if ($t === '') {
|
||||
continue;
|
||||
}
|
||||
if (!$first_at) {
|
||||
$first_at = $t;
|
||||
}
|
||||
$last_at = $t;
|
||||
if ($ct === 'masuk') {
|
||||
$has_masuk = true;
|
||||
} elseif ($ct === 'pulang') {
|
||||
$has_pulang = true;
|
||||
} else {
|
||||
$timeOnly = date('H:i:s', strtotime($t));
|
||||
if ($timeOnly < '12:00:00') {
|
||||
$has_masuk = true;
|
||||
}
|
||||
if ($timeOnly >= '13:00:00') {
|
||||
$has_pulang = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'has_masuk' => $has_masuk,
|
||||
'has_pulang' => $has_pulang,
|
||||
'first_at' => $first_at,
|
||||
'last_at' => $last_at,
|
||||
'date' => $today,
|
||||
];
|
||||
|
||||
return $this->successResponse($data, 'OK');
|
||||
}
|
||||
}
|
||||
76
app/Modules/Mobile/Controllers/FacePhotoController.php
Normal file
76
app/Modules/Mobile/Controllers/FacePhotoController.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Mobile\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Academic\Models\StudentModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Mengambil foto wajah referensi siswa untuk verifikasi di aplikasi mobile.
|
||||
* Foto disimpan di writable/faces/{student_id}.jpg (atau .png).
|
||||
* Sumber foto bisa dari sync Google Drive (folder Foto Siswa per kelas).
|
||||
*/
|
||||
class FacePhotoController extends BaseApiController
|
||||
{
|
||||
/** Subfolder di writable untuk foto wajah */
|
||||
protected string $facesDir = 'faces';
|
||||
|
||||
/**
|
||||
* GET /api/mobile/student/face-photo?student_id=123
|
||||
* Mengembalikan gambar foto wajah siswa atau 404 jika belum ada.
|
||||
*/
|
||||
public function show(): ResponseInterface
|
||||
{
|
||||
$studentId = (int) $this->request->getGet('student_id');
|
||||
if ($studentId < 1) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'student_id wajib',
|
||||
]);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->find($studentId);
|
||||
if (! $student) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Siswa tidak ditemukan',
|
||||
]);
|
||||
}
|
||||
|
||||
$writable = defined('WRITEPATH') ? WRITEPATH : rtrim(realpath(FCPATH . '..' . DIRECTORY_SEPARATOR . 'writable') ?: FCPATH, DIRECTORY_SEPARATOR);
|
||||
$facesPath = $writable . DIRECTORY_SEPARATOR . $this->facesDir;
|
||||
|
||||
$extensions = ['jpg', 'jpeg', 'png'];
|
||||
$filePath = null;
|
||||
foreach ($extensions as $ext) {
|
||||
$p = $facesPath . DIRECTORY_SEPARATOR . $studentId . '.' . $ext;
|
||||
if (is_file($p)) {
|
||||
$filePath = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($filePath === null) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Foto wajah belum diunggah untuk siswa ini. Hubungi admin.',
|
||||
]);
|
||||
}
|
||||
|
||||
$mime = mime_content_type($filePath);
|
||||
if (! in_array($mime, ['image/jpeg', 'image/png'], true)) {
|
||||
$mime = 'image/jpeg';
|
||||
}
|
||||
|
||||
$faceHash = isset($student->face_hash) ? (string) $student->face_hash : md5_file($filePath);
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', $mime)
|
||||
->setHeader('Cache-Control', 'public, max-age=31536000')
|
||||
->setHeader('X-Face-Hash', $faceHash)
|
||||
->setHeader('Access-Control-Allow-Origin', '*')
|
||||
->setBody(file_get_contents($filePath));
|
||||
}
|
||||
}
|
||||
275
app/Modules/Mobile/Controllers/RegistrationController.php
Normal file
275
app/Modules/Mobile/Controllers/RegistrationController.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Mobile\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Academic\Models\ClassModel;
|
||||
use App\Modules\Academic\Models\StudentModel;
|
||||
use App\Modules\Mobile\Models\StudentMobileAccountModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class RegistrationController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* POST /api/mobile/login
|
||||
*
|
||||
* Body: { "nisn": "...", "pin": "..." }
|
||||
*/
|
||||
public function login(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$nisn = trim((string) ($payload['nisn'] ?? ''));
|
||||
$pin = (string) ($payload['pin'] ?? '');
|
||||
|
||||
if ($nisn === '' || $pin === '') {
|
||||
return $this->errorResponse('NISN dan PIN wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->findByNisn($nisn);
|
||||
|
||||
if (! $student) {
|
||||
return $this->errorResponse('Siswa dengan NISN tersebut tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ((int) ($student->is_active ?? 1) === 0) {
|
||||
return $this->errorResponse('Siswa ini tidak aktif', null, null, ResponseInterface::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$accountRow = $accountModel->where('student_id', (int) $student->id)->first();
|
||||
|
||||
if (! $accountRow || empty($accountRow['pin_hash']) || ! password_verify($pin, $accountRow['pin_hash'])) {
|
||||
return $this->errorResponse('PIN salah', null, null, ResponseInterface::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$classModel = new ClassModel();
|
||||
/** @var \App\Modules\Academic\Entities\ClassEntity|null $class */
|
||||
$class = null;
|
||||
if ($student->class_id) {
|
||||
$class = $classModel->find($student->class_id);
|
||||
}
|
||||
|
||||
$classLabel = '-';
|
||||
if ($class !== null) {
|
||||
$parts = array_filter([
|
||||
trim((string) ($class->grade ?? '')),
|
||||
trim((string) ($class->major ?? '')),
|
||||
trim((string) ($class->name ?? '')),
|
||||
]);
|
||||
$label = implode(' ', $parts);
|
||||
$classLabel = $label !== '' ? $label : ('Kelas #' . (int) $class->id);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'student_id' => (int) $student->id,
|
||||
'name' => (string) ($student->name ?? ''),
|
||||
'nisn' => (string) ($student->nisn ?? ''),
|
||||
'class_id' => $student->class_id ? (int) $student->class_id : null,
|
||||
'class_label' => $classLabel,
|
||||
];
|
||||
|
||||
return $this->successResponse($data, 'Login berhasil');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mobile/register/nisn
|
||||
*
|
||||
* Body: { "nisn": "..." }
|
||||
*/
|
||||
public function checkNisn(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$nisn = trim((string) ($payload['nisn'] ?? ''));
|
||||
|
||||
if ($nisn === '') {
|
||||
return $this->errorResponse('NISN wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->findByNisn($nisn);
|
||||
|
||||
if (! $student) {
|
||||
return $this->errorResponse('Siswa dengan NISN tersebut tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ((int) ($student->is_active ?? 1) === 0) {
|
||||
return $this->errorResponse('Siswa ini tidak aktif', null, null, ResponseInterface::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Sudah punya akun mobile (PIN) → arahkan ke login
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$accountRow = $accountModel->where('student_id', (int) $student->id)->first();
|
||||
if ($accountRow && ! empty($accountRow['pin_hash'])) {
|
||||
return $this->errorResponse('NISN ini sudah terdaftar. Silakan masuk.', null, null, ResponseInterface::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$classModel = new ClassModel();
|
||||
$classes = $classModel->orderBy('grade', 'ASC')
|
||||
->orderBy('major', 'ASC')
|
||||
->orderBy('name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
$availableClasses = [];
|
||||
foreach ($classes as $c) {
|
||||
$parts = array_filter([
|
||||
trim((string) ($c->grade ?? '')),
|
||||
trim((string) ($c->major ?? '')),
|
||||
trim((string) ($c->name ?? '')),
|
||||
]);
|
||||
$label = implode(' ', $parts);
|
||||
$availableClasses[] = [
|
||||
'id' => (int) $c->id,
|
||||
'label' => $label !== '' ? $label : ('Kelas #' . (int) $c->id),
|
||||
];
|
||||
}
|
||||
|
||||
$currentClassId = $student->class_id ? (int) $student->class_id : null;
|
||||
$currentClassLabel = null;
|
||||
if ($currentClassId !== null) {
|
||||
foreach ($availableClasses as $cls) {
|
||||
if ($cls['id'] === $currentClassId) {
|
||||
$currentClassLabel = $cls['label'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'student_id' => (int) $student->id,
|
||||
'name' => (string) ($student->name ?? ''),
|
||||
'nisn' => (string) ($student->nisn ?? ''),
|
||||
'current_class_id' => $currentClassId,
|
||||
'current_class_label' => $currentClassLabel,
|
||||
'available_classes' => $availableClasses,
|
||||
];
|
||||
|
||||
return $this->successResponse($data, 'NISN valid');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mobile/register/complete
|
||||
*
|
||||
* Body: { "student_id": 0, "class_id": 0, "pin": "123456" }
|
||||
*/
|
||||
public function complete(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$studentId = (int) ($payload['student_id'] ?? 0);
|
||||
$classId = (int) ($payload['class_id'] ?? 0);
|
||||
$pin = (string) ($payload['pin'] ?? '');
|
||||
|
||||
if ($studentId <= 0) {
|
||||
return $this->errorResponse('student_id wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
if ($classId <= 0) {
|
||||
return $this->errorResponse('Kelas wajib dipilih', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
$pin = trim($pin);
|
||||
if ($pin === '' || strlen($pin) < 4) {
|
||||
return $this->errorResponse('PIN minimal 4 karakter', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$classModel = new ClassModel();
|
||||
|
||||
$student = $studentModel->find($studentId);
|
||||
if (! $student) {
|
||||
return $this->errorResponse('Siswa tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$class = $classModel->find($classId);
|
||||
if (! $class) {
|
||||
return $this->errorResponse('Kelas tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Update class_id siswa (mapping pertama kali / koreksi)
|
||||
$studentModel->update($studentId, ['class_id' => $classId]);
|
||||
|
||||
// Simpan / update akun mobile siswa
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$pinHash = password_hash($pin, PASSWORD_BCRYPT);
|
||||
|
||||
$exists = $accountModel->where('student_id', $studentId)->first();
|
||||
if ($exists) {
|
||||
$accountModel->update((int) $exists['id'], [
|
||||
'pin_hash' => $pinHash,
|
||||
]);
|
||||
} else {
|
||||
$accountModel->insert([
|
||||
'student_id' => $studentId,
|
||||
'pin_hash' => $pinHash,
|
||||
]);
|
||||
}
|
||||
|
||||
$classLabelParts = array_filter([
|
||||
trim((string) ($class->grade ?? '')),
|
||||
trim((string) ($class->major ?? '')),
|
||||
trim((string) ($class->name ?? '')),
|
||||
]);
|
||||
$classLabel = implode(' ', $classLabelParts);
|
||||
|
||||
$data = [
|
||||
'student_id' => $studentId,
|
||||
'name' => (string) ($student->name ?? ''),
|
||||
'nisn' => (string) ($student->nisn ?? ''),
|
||||
'class_id' => (int) $class->id,
|
||||
'class_label' => $classLabel !== '' ? $classLabel : ('Kelas #' . (int) $class->id),
|
||||
];
|
||||
|
||||
return $this->successResponse($data, 'Registrasi mobile berhasil');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mobile/forgot-pin
|
||||
*
|
||||
* Body: { "nisn": "...", "new_pin": "1234", "new_pin_confirm": "1234" }
|
||||
*/
|
||||
public function forgotPin(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$nisn = trim((string) ($payload['nisn'] ?? ''));
|
||||
$newPin = (string) ($payload['new_pin'] ?? '');
|
||||
$newPinConfirm = (string) ($payload['new_pin_confirm'] ?? '');
|
||||
|
||||
if ($nisn === '') {
|
||||
return $this->errorResponse('NISN wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$newPin = trim($newPin);
|
||||
$newPinConfirm = trim($newPinConfirm);
|
||||
if ($newPin === '' || strlen($newPin) < 4) {
|
||||
return $this->errorResponse('PIN baru minimal 4 karakter', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
if ($newPin !== $newPinConfirm) {
|
||||
return $this->errorResponse('PIN baru dan konfirmasi tidak sama', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$studentModel = new StudentModel();
|
||||
$student = $studentModel->findByNisn($nisn);
|
||||
|
||||
if (! $student) {
|
||||
return $this->errorResponse('Siswa dengan NISN tersebut tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ((int) ($student->is_active ?? 1) === 0) {
|
||||
return $this->errorResponse('Siswa ini tidak aktif', null, null, ResponseInterface::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$accountModel = new StudentMobileAccountModel();
|
||||
$accountRow = $accountModel->where('student_id', (int) $student->id)->first();
|
||||
|
||||
if (! $accountRow || empty($accountRow['pin_hash'])) {
|
||||
return $this->errorResponse('NISN ini belum terdaftar. Silakan daftar dulu.', null, null, ResponseInterface::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$pinHash = password_hash($newPin, PASSWORD_BCRYPT);
|
||||
$accountModel->update((int) $accountRow['id'], ['pin_hash' => $pinHash]);
|
||||
|
||||
return $this->successResponse(
|
||||
['student_id' => (int) $student->id],
|
||||
'PIN berhasil direset. Silakan masuk dengan PIN baru.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user