# SUPERAPP ACADEMY V17 — SOURCE LENGKAP & PANDUAN PEMULA

**Versi**: 17.0.0 — CodeIgniter 4 / PHP 8.2+ / MySQL-MariaDB

## 0. PENTING SEBELUM MENGGANTI KODE

Paket ini lengkap (`app/`, `system/`, `public/`, `writable/`, `.env` contoh, `database/`, `tests/`).
File `.env` bawaan sengaja berisi **placeholder**. **JANGAN MENIMPA NILAI .env SERVER** tanpa menyalin kembali konfigurasi database, domain, Google OAuth, payment gateway dan WA Anda.
Jangan menghapus `writable/uploads/academy`, file peserta, atau data transaksi.

### Anda sudah memakai V16 dengan database berisi data

1. Backup database melalui phpMyAdmin dan backup seluruh direktori aplikasi.
2. Simpan file `.env` lama di luar folder publik / backup.
3. Jalankan `database/UPGRADE_V16_KE_V17.sql` **SATU KALI** pada database V16. File ini menambah kolom tanpa menjatuhkan tabel.
4. Ekstrak isi ZIP V17 ke root aplikasi, lalu pulihkan nilai `.env` yang asli. Tidak perlu mengimpor SQL instalasi awal.
5. Document Root tetap menunjuk direktori `public/`.
6. Tes menu `/keranjang` dengan produk berbayar, produk gratis dan produk pilihan member.
7. Buka panel admin → Pengaturan untuk memilih cache dan membersihkan cache publik setelah perubahan.
8. Pada tahap pengujian awal gunakan transaksi sandbox dan akun uji terpisah.

### Baru pertama kali memasang, database benar-benar kosong

1. Buat database MySQL/MariaDB kosong.
2. Impor **hanya** `database/IMPORT_SEKALI.sql`. Jangan menjalankan SQL upgrade tambahan.
3. Lengkapi `.env` dengan host, database, username, password, URL HTTPS, dan OAuth.
4. Arahkan Document Root ke `public/`, aktifkan ekstensi `intl`, `mbstring`, `mysqli`, `openssl` dan `json`.
5. Buka website, login, jadikan akun Anda admin sesuai panduan instalasi sebelumnya.

## 1. Perbaikan error keranjang (V16)

- `app/Services/CartService.php` mengambil **`p.id AS id`** pada query `JOIN products`.
- `app/Services/ProductTierService.php` juga dapat membaca `product_id` jika kode lain mengirim format hasil JOIN.
- Produk/tier tidak aktif, konten yang diarsipkan atau kedaluwarsa diberi pesan aman dan dapat dihapus dari keranjang.
- Backend memvalidasi ulang harga dan ketersediaan saat checkout. Harga HTML/JavaScript tidak pernah dipercaya.

## 2. Masa berlaku untuk Quiz, Tryout, Materi, Pelatihan, Kelas, Produk & Member

- **Ikuti produk**: konten mengikuti masa aktif paket yang dibeli.
- **Durasi**: 1–3650 hari sejak checkout berhasil (bukan sejak klik `Tambah Keranjang`).
- **Tanggal akhir**: sampai 23.59.59 pada tanggal tersebut, zona Asia/Makassar/WITA.
- **Kombinasi**: tanggal lebih awal antara durasi dan tanggal akhir.
- **Tanpa batas**: tidak kedaluwarsa kecuali ada aturan paket induk atau status konten berubah.

Contoh: kelas dibeli 10 Nov 2026 pukul 12.00, 30 hari → berakhir 10 Des 2026 pukul 12.00.
Jika materi di kelas itu dibatasi sampai 30 Nov 2026, materi terkunci pada 1 Des meski kelas masih aktif.
Ketika seseorang membeli akses yang sama melalui dua sumber yang masih sah, sistem tetap membuka konten bila minimal satu sumber memenuhi syarat.

**PENTING**: Hak akses dari kelas/materi disertai asal pembelian, bukan sekadar `hasAccess()` pada tombol.
Tanggal perpanjangan membership disimpan dalam `access_grants.starts_at` agar perpanjangan yang dijadwalkan tidak membuka akses lebih awal.

### Setelah kedaluwarsa

- Program masih muncul pada **Belajar Saya** dengan status `Kedaluwarsa` dan tautan perpanjang.
- Riwayat transaksi dan ringkasan nilai dari `Riwayat Pengerjaan` tetap dapat dilihat.
- Pembahasan lengkap, video, PDF, materi premium, serta submit jawaban baru diblok di server.
- Jawaban CAT yang sudah tersimpan tidak dibuang apabila akses habis di tengah ujian.
- Perpanjangan menciptakan catatan hak akses baru; riwayat lama tidak dihapus.

## 3. Jenis konten ≠ Bundel Member

- Quiz/tryout dengan banyak bank soal tetap **SATU produk ujian**.
- Materi berisi video/latihan tetap **SATU materi**.
- Kelas berisi banyak bab/quiz tetap **SATU kelas online**.
- Bundel (`product_type=bundle`) wajib `pricing_mode=membership`, minimal dua isi dan hanya sumber `material` atau `course`.
- Bundel lama yang tidak mengikuti aturan ini diarsipkan oleh migrasi (tidak dihapus). Cek dan ubah lewat panel admin.
- Harga/member ditentukan melalui tier Silver, Gold, Platinum pada menu `Paket Member`.

## 4. Cache publik, performa dan admin

- Beranda: produk baru, review yang terlihat, statistik, paket member → cache selama 300 detik secara default.
- Katalog: hasil daftar yang tidak memakai pencarian → cache berdasarkan jenis dan halaman, **bukan cache semua data sekaligus**.
- Pencarian bebas tidak dicache supaya kata kunci tak membuat ribuan cache key.
- Statistik kuota WAR selalu **langsung dari database**, tidak dimasukkan ke cache.
- CSS publik lama versi V8 sampai V15 digabung ke `public/assets/v17/public.css` (tidak minify).
- JavaScript publik digabung ke `public/assets/v17/public.js` (tidak minify).
- Menu `Panel Admin → Pengaturan → Manajemen Cache` memiliki opsi hidup/mati, durasi 30–3600 detik, dan tombol reset cache dengan POST + CSRF.
- Tombol reset menggunakan pergantian nomor versi cache. Ini **tidak menghapus session/login, keranjang, atau transaksi**.

## 5. Folder utama yang boleh Anda pelajari

```text
app/
  Services/
    AccessPolicyService.php  <-- cara menilai durasi & tanggal
    AccessGrantService.php   <-- pemeriksa hak akses riil, kelas turunan
    ProductTierService.php   <-- harga pilihan member
    CartService.php          <-- query keranjang & checkout
    FulfillmentService.php   <-- aktivasi akses setelah pembayaran sah
    PublicCacheService.php   <-- cache beranda/katalog dan invalidasi
    AttemptService.php       <-- CAT, batas akses di tengah percobaan
  Controllers/
    Admin/ProductController.php  <-- validasi bundel dan harga
    Admin/SettingsController.php <-- cache admin
    Web/PublicController.php     <-- beranda dan katalog
    Web/MemberController.php     <-- riwayat akses terkunci
  Views/
    admin/_v17_access_fields.php <-- form masa akses, digunakan berulang
    web/library.php              <-- kartu riwayat aktif/expired
    web/expired_result.php       <-- skor ringkas saja saat terkunci
public/assets/v17/
  public.css                  <-- CSS publik gabungan, tanpa minify
  public.js                   <-- script publik gabungan
  admin.js                    <-- interaksi form masa akses

database/
  IMPORT_SEKALI.sql           <-- HANYA database baru kosong
  UPGRADE_V16_KE_V17.sql      <-- HANYA database lama V16
tests/
  smoke_v17.php               <-- php tests/smoke_v17.php
```

## 6. Pola belajar mengembangkan kode

**Route → Controller → Service → Database → View**.

Contoh menambah metode masa akses:
1. Ubah whitelist opsi di `AccessPolicyService::fromInput()`.
2. Implementasikan perhitungan di `AccessPolicyService::expiry()`.
3. Tambahkan pilihan UI di `_v17_access_fields.php`.
4. Ubah database dengan SQL upgrade BARU, jangan edit SQL migrasi yang sudah dijalankan.
5. Buat pengujian baru di `tests/`, lalu cek dengan `php -l` dan uji melalui browser.

Untuk troubleshooting, buka `writable/logs/log-YYYY-MM-DD.log` dan cari `CRITICAL`.
Jangan mengaktifkan mode development permanen di server publik.

## 7. Pemeriksaan yang dilakukan saat pembuatan V17

- Syntax PHP pada source diperiksa menggunakan `php -l`.
- Syntax JavaScript diperiksa dengan `node --check`.
- Smoke test sebelumnya dan V17 dijalankan; `tests/smoke_v17.php` juga memeriksa logika waktu secara nyata.
- Pengujian browser nyata, MySQL concurrency, gateway callback dan kuota WAR di hosting **belum dapat dijalankan di lingkungan pengembangan ini**. Lakukan di staging terlebih dahulu.

## 8. Hindari error Whoops setelah timpa

Urutannya **backup → migrasi SQL V17 → ekstrak file V17 → pulihkan .env riil → uji**. Jika belum menjalankan SQL, controller yang memerlukan kolom `access_mode`/`expiry_mode` akan gagal. Jangan mengimpor ulang `IMPORT_SEKALI.sql` pada database berisi data.
