feat: API wali murid (Sanctum) + verifikasi admin

- Pasang laravel/sanctum, routes/api.php (prefix api/v1, 10 endpoint)
- Migrasi: parent_name di students, parent_students (pivot wali↔anak),
  parent_devices (token FCM), permission parents-verify
- Auth wali: registrasi NISN+HP (auto-verify jika HP cocok data siswa,
  pending jika tidak) + login token Sanctum
- Endpoint: me, logout, students, link anak, violations, sp-letters,
  notifications, register device
- Halaman admin Verifikasi Wali (approve/reject) + menu sidebar
- Sync kesiswaan: tarik data orang tua (student_parents) ke students
- 8 feature test baru (ParentApiTest) — total suite 39 test hijau
This commit is contained in:
Novy San87
2026-08-05 18:58:29 +07:00
parent b06419624b
commit 3efce0f919
24 changed files with 1437 additions and 4 deletions
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};
@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('students', function (Blueprint $table) {
if (! Schema::hasColumn('students', 'parent_name')) {
$table->string('parent_name', 150)->nullable()->after('parent_phone');
}
});
}
public function down(): void
{
Schema::table('students', function (Blueprint $table) {
if (Schema::hasColumn('students', 'parent_name')) {
$table->dropColumn('parent_name');
}
});
}
};
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('parent_students', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('student_id')->constrained('students')->cascadeOnDelete();
// relation: father | mother | guardian (sesuai student_parents di kesiswaan)
$table->string('relation', 20)->nullable();
// status: pending (menunggu verifikasi admin) | active | rejected
$table->string('status', 20)->default('pending')->index();
$table->timestamp('verified_at')->nullable();
$table->unsignedBigInteger('verified_by')->nullable();
$table->string('rejection_reason', 255)->nullable();
$table->timestamps();
$table->unique(['user_id', 'student_id']);
$table->index(['student_id', 'status']);
});
}
public function down(): void
{
Schema::dropIfExists('parent_students');
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('parent_devices', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->string('platform', 20)->default('android'); // android | ios
$table->string('fcm_token', 255)->nullable();
$table->string('device_name', 100)->nullable();
$table->timestamp('last_active_at')->nullable();
$table->timestamps();
$table->index(['user_id', 'fcm_token']);
});
}
public function down(): void
{
Schema::dropIfExists('parent_devices');
}
};
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$now = now();
$id = DB::table('permissions')->insertGetId([
'key' => 'parents-verify',
'group' => 'wali',
'label' => 'Verifikasi Akun Wali Murid',
'description' => 'Menyetujui/menolak akun wali murid yang mendaftar dari aplikasi mobile',
'created_at' => $now,
'updated_at' => $now,
]);
// Role yang berhak memverifikasi
foreach (['admin', 'bk', 'waka_kesiswaan', 'kepala_sekolah', 'ketua_tim'] as $role) {
DB::table('role_permissions')->insertOrIgnore([
'role' => $role,
'permission_id' => $id,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function down(): void
{
$ids = DB::table('permissions')->where('key', 'parents-verify')->pluck('id');
DB::table('role_permissions')->whereIn('permission_id', $ids)->delete();
DB::table('permissions')->where('key', 'parents-verify')->delete();
}
};