Skip to main content

Arsitektur Teknis PurwaExam

Studi Kasus Teknis Version 1.0

Asal-Usul: Dari APIK ke PurwaExam

PurwaExam adalah generasi penerus APIK (Aplikasi Ujian Kompetensi), sistem lama berbasis CodeIgniter 3 HMVC. Konsep struktur akademik (rumpun → sub-rumpun → Learning Outcome), bank soal per Learning Outcome, dan token akses ujian diwariskan langsung — tapi seluruh model data dan implementasinya dibangun ulang dari nol di atas Laravel 13 dan Filament v4, untuk mengakomodasi kebutuhan yang tidak ada di APIK: ujian akhir per kelas dengan bank soal rahasia.

Stack

  • Backend: Laravel 13, PHP 8.3
  • Admin panel: Filament v4 — resource, relation manager, importer bawaan dipakai penuh, minim custom UI dari nol
  • Autorisasi: spatie/laravel-permission (role & permission per panel)
  • Audit: spatie/laravel-activitylog — dicatat di level model (ExamPackage, ExamBatch, ExamToken, Question, User)
  • Backup: spatie/laravel-backup, dump database harian, retensi 30 hari

Model Kepemilikan Soal

Satu method jadi sumber kebenaran tunggal untuk “soal mana yang boleh dipakai di ujian ini”:

Question::eligibleForExam(LearningOutcome $lo, Exam $exam): HasMany

Dipakai identik di tiga tempat: hitungan stok soal (ExamComponent::availableQuestionCount()), snapshot soal saat ujian dimulai (AttemptService::snapshotQuestions()), dan preview live di form admin. Untuk ujian akhir (kelas_id terisi), hasilnya union soal milik seluruh dosen pengampu kelas. Untuk ujian kompetensi (program_id, tanpa kelas_id), tidak ada pembatasan — kolaboratif lintas dosen by design.

Dua Tabel Pivot Dosen — Kenapa Perlu Disinkron Manual

course_lecturer (dosen ↔ mata kuliah, mengatur akses Bank Soal & Learning Outcome) dan kelas_lecturer (dosen ↔ kelas, mengatur akses edit Kelas) adalah dua tabel terpisah yang tidak otomatis sinkron. Menugaskan dosen lewat form Kelas cuma menulis ke kelas_lecturer.

Perbaikan: Kelas::syncLecturersToCourse(), union-only lewat syncWithoutDetaching (tidak pernah mencabut akses), dipanggil dari afterCreate()/afterSave() di halaman Filament — bukan event model saved(), karena event itu terpanggil sebelum Filament selesai menyimpan relasi form-nya sendiri.

Deploy di Shared Hosting Tanpa Custom Document Root

Kalau document root domain terkunci ke folder bawaan (tidak bisa diarahkan ke public/), struktur yang dipakai:

/home/USER/nama-app/              → kode aplikasi penuh, tidak web-accessible
/home/USER/domain.com/            → HANYA isi public/, + index.php yang dimodifikasi

index.php di folder domain butuh path require disesuaikan (../nama-app/vendor/autoload.php, bukan ../vendor/autoload.php) karena kedua folder sejajar, bukan induk-anak. Hasil npm run build wajib disinkronkan ke dua tempat: nama-app/public/build/ (dibaca PHP untuk manifest Vite) dan domain.com/build/ (diakses browser). Symlink storage juga tidak bisa pakai artisan storage:link default — harus dibuat manual menyasar folder document root yang benar.

Ekstensi PHP yang Sering Terlewat di Shared Hosting

zip dan intl tidak selalu aktif default. config/backup.php mereferensikan ZipArchive di level file config yang dievaluasi setiap request — tanpa guard extension_loaded('zip'), satu baris ini bisa menjatuhkan seluruh aplikasi, bukan cuma perintah backup.

Referensi Terkait

Pelayanan

Butuh website atau aplikasi?

Sashindo Project membangun company profile, aplikasi web, LMS, dan otomasi Google Apps Script sesuai kebutuhan bisnis Anda.

Lihat layanan