# SIMAP API — Aplikasi Wali Murid

REST API berbasis PHP native untuk aplikasi Flutter `SIMAP Wali`.
Semua logika bisnis (absensi, penilaian, gamifikasi, notifikasi) dipakai ulang
dari folder `web/`, sehingga tidak ada duplikasi aturan antara web dan mobile.

---

## Pemasangan

Arahkan subdomain atau subfolder ke `api/index.php`.

```apache
# Contoh: https://sekolah.sch.id/api
Alias /api /home/user/simap/api
<Directory /home/user/simap/api>
    AllowOverride All
    Require all granted
</Directory>
```

Berkas `api/.htaccess` sudah menangani rewrite ke `index.php` sekaligus
meneruskan header `Authorization` (sebagian hosting memotongnya).

Ubah alamat API di aplikasi lewat `--dart-define`:

```bash
flutter build apk --release --dart-define=SIMAP_API=https://sekolah.sch.id/api
```

---

## Bentuk balasan

Berhasil:

```json
{ "ok": true, "data": { ... }, "meta": { ... } }
```

Gagal:

```json
{ "ok": false, "pesan": "Sesi berakhir. Silakan masuk kembali." }
```

Kode status yang dipakai: `200` berhasil, `401` token tidak sah/kedaluwarsa,
`403` bukan hak akses akun tersebut, `404` tidak ditemukan, `409` bentrok data,
`422` isian tidak valid, `429` terlalu sering, `500` galat server.

---

## Autentikasi

Token bearer disimpan di tabel `user_token`, masa berlaku 30 hari
(`API_TOKEN_TTL` di `web/config/config.php`).

```
Authorization: Bearer <token>
```

Satu `device_id` hanya menyimpan satu token; login ulang di perangkat sama
akan mencabut token lama perangkat itu.

### POST `/auth/login`

```json
{ "username": "081234567890", "password": "rahasia",
  "device_id": "abc-123", "device_name": "Redmi Note 12",
  "fcm_token": "…", "platform": "ANDROID" }
```

Menerima nomor dalam format `08xx`, `62xx`, maupun `+62xx` — dinormalkan
oleh `normalHp()`. Login ditolak bila peran akun bukan `ORTU`; guru dan staf
memakai versi web. Ada rem 5 percobaan gagal per 5 menit.

Balasan berisi `token`, `expired_at`, `harus_ganti_sandi`, `pengguna`, `sekolah`.

### Endpoint sesi lain

| Metode | Jalur | Keterangan |
|---|---|---|
| POST | `/auth/logout` | Cabut token perangkat ini |
| POST | `/auth/ganti-sandi` | `sandi_lama`, `sandi_baru` (min 6 karakter); token perangkat lain dicabut |
| POST | `/auth/perangkat` | Perbarui `fcm_token` saat aplikasi dibuka |

---

## Penjaga kepemilikan data

Setiap endpoint beranama `/anak/{id}/…` melewati `Token::anakSaya($id)`, yang
memverifikasi baris di `siswa_ortu` untuk ortu yang sedang masuk. Bila tidak
cocok, balasan `403` — bukan `404` — dan kueri data tidak pernah dijalankan.
Ini penjaga utama supaya data anak orang lain tidak bisa dibaca hanya dengan
menebak nomor id.

---

## Daftar endpoint

### Anak

| Metode | Jalur | Isi balasan |
|---|---|---|
| GET | `/anak` | Semua anak pada akun ini + poin, level, status absen hari ini |
| GET | `/anak/{id}/beranda` | Absen hari ini, rekap bulan, jadwal, tugas menunggu, nilai terbaru, tagihan, pengumuman |
| GET | `/anak/{id}/profil` | Identitas lengkap, kartu RFID, ekstrakurikuler, prestasi |

### Akademik

| Metode | Jalur | Catatan |
|---|---|---|
| GET | `/anak/{id}/absensi?bulan=2026-08` | Rekap + rincian harian + daftar hari libur |
| GET | `/anak/{id}/nilai` | Per mapel: formatif, sumatif, STS, SAS, nilai akhir, deskripsi, rincian butir; plus capaian dimensi |
| GET | `/anak/{id}/rapor` | Hanya rapor berstatus `TERBIT` |
| GET | `/rapor/{id}` | Isi lengkap; menandai `dibuka_ortu` |
| GET | `/anak/{id}/tugas` | Status pengumpulan, nilai, umpan balik guru |
| GET | `/anak/{id}/literasi` | Ringkas bulan ini, riwayat jurnal, buku digital yang sedang dibaca |
| GET | `/anak/{id}/laporan-harian` | Ringkasan aktivitas yang dibuat cron tiap sore |
| GET | `/anak/{id}/perilaku` | Catatan guru yang ditandai `lapor_ortu`; sekaligus menandai sudah dibaca |

Capaian dimensi memakai kerangka aktif sekolah: **8 Dimensi Profil Lulusan**
untuk sekolah di bawah Kemendikdasmen, atau **Panca Cinta** untuk madrasah
di bawah Kemenag. Ditentukan oleh `kerangkaDimensi()` yang membaca
`sekolah.naungan` dan `sekolah.kurikulum_aktif`.

### Layanan

| Metode | Jalur | Catatan |
|---|---|---|
| POST | `/anak/{id}/izin` | Ajukan izin/sakit, maksimal 14 hari, tolak bila bentrok |
| GET | `/anak/{id}/izin` | Riwayat + catatan penyetuju |
| GET | `/anak/{id}/tagihan` | Tagihan, tunggakan, riwayat pembayaran |
| GET | `/notifikasi` | 60 terbaru + `meta.belum_dibaca` |
| POST | `/notifikasi/baca` | `id` tertentu, atau kosongkan untuk semua |
| GET | `/pengumuman` | Sasaran `SEMUA` atau `ORTU`, dalam rentang tampil |
| GET | `/pesan?anak_id=` | Percakapan dengan wali kelas; menandai pesan masuk sudah dibaca |
| POST | `/pesan` | `anak_id`, `isi` (maks 1000 karakter, maks 20 pesan/jam) |

#### Lampiran izin

Dikirim sebagai base64 (`lampiran_base64` + `lampiran_nama`), maksimal 3 MB,
format JPG/PNG/WEBP/PDF. Isi berkas diperiksa dengan `getimagesizefromstring()`
atau tanda `%PDF` — bukan hanya ekstensinya — lalu disimpan dengan nama acak
di `storage/uploads/izin/`.

---

## Catatan keamanan

- Kata sandi memakai `password_hash()` bawaan PHP; tidak pernah dikembalikan API.
- Token acak 64 karakter heksadesimal dari `random_bytes(32)`.
- Semua kueri memakai prepared statement lewat `Database`.
- CORS dibatasi ke `APP_URL` saat `APP_ENV=production`.
- Token kedaluwarsa dibersihkan otomatis setiap kali ada penerbitan token baru.
- Sebelum go-live: ganti `APP_KEY`, `WA_GATEWAY_KEY`, dan pastikan
  `SIMAP_ENV=production` agar pesan galat rinci tidak bocor ke klien.

---

## Struktur berkas

```
api/
├── index.php              peta rute
├── .htaccess              rewrite + teruskan Authorization
├── core/
│   ├── Bootstrap.php      memuat config & model dari web/
│   ├── Api.php            format balasan, pembacaan input
│   ├── Token.php          terbitkan/verifikasi token, penjaga kepemilikan
│   ├── Pengumuman.php     pembacaan pengumuman sasaran ortu
│   └── RouterApi.php      router pola {param}
└── controllers/
    ├── AuthApi.php
    ├── AnakApi.php
    ├── AkademikApi.php
    └── LayananApi.php
```
