elemes/services/token_service.py

60 lines
1.9 KiB
Python
Raw Normal View History

2026-03-25 09:39:51 +07:00
"""
Token & progress facade — kontrak publik yang dipakai routes.
Mendelegasikan ke satu-satunya storage backend aktif: PostgreSQL
(services.storage.postgres_backend). Backend CSV sudah dicabut setelah
cutover penuh — routes TIDAK boleh tahu detail backend, cukup import sini.
2026-03-25 09:39:51 +07:00
"""
from services.storage import get_backend_module
def _backend():
return get_backend_module()
def validate_token(token):
"""Valid token → {'student_name', 'is_teacher'} | None."""
return _backend().validate_token(token)
def is_teacher_token(token):
return _backend().is_teacher_token(token)
2026-03-25 09:39:51 +07:00
def get_student_progress(token):
"""Dict {lesson_slug: status} | None."""
return _backend().get_student_progress(token)
2026-03-25 09:39:51 +07:00
def has_quiz_attempt(token, lesson_name):
"""Cek apakah siswa sudah memiliki rekaman attempt kuis untuk lesson."""
return _backend().has_quiz_attempt(token, lesson_name)
feat(quiz): izinkan guru melanjutkan attempt terminated dari jawaban tersimpan Kebijakan termination tetap strict, tapi guru pegang override untuk kendala tak terduga (false-positive blur dari dialog OS, browser crash, refresh tak sengaja). Selain reset (siswa mengulang dari nol), guru kini bisa memberi izin resume — siswa melanjutkan dari jawaban yang sudah tersimpan. - Tambah kolom quiz_attempts.resumed_at (migrasi 0006) sebagai penanda izin guru melanjutkan attempt terminated - Tambah endpoint teacher-only POST /resume-quiz-attempt — kontrak keamanan sama dengan /reset-progress (guru cukup student_id anonim, tanpa token siswa) - Syarat: hanya attempt ber-status terminated yang bisa di-resume; submitted ditolak karena kuis sudah selesai normal - has_quiz_attempt meloloskan attempt ber-izin sehingga gate one-attempt terbuka selama sesi lanjutan - finalize_quiz_attempt menimpa row attempt sekali (status/skor/answers baru) lalu clear resumed_at; retry berikutnya kembali ke jalur idempotent normal - Sinyal ke frontend siswa lewat quiz_resumable pada data lesson; laporan guru menampilkan badge "Dilanjutkan" dan tombol "Lanjutkan" untuk attempt terminated - Frontend: restoreQuizSession() merekonstruksi sesi dari jawaban tersimpan per question_id, is_correct dihitung ulang dari source sebagai guard bila konten soal berubah; option_id tak dikenal dianggap belum dijawab - Frontend: firstUnansweredIndex() menentukan soal awal sesi lanjutan; resolveQuizTabView() memusatkan keputusan view agar transisi resume dapat diuji Vitest - UI siswa: panel "Lanjutkan Kuis" + snackbar mode lanjutan (sekali muncul). Attempt di-refetch segar sebelum sesi dimulai agar izin yang sudah dicabut guru tidak memulai sesi yang jawabannya akan terbuang - Test: 16 integrasi backend (services/tests/test_resume_quiz.py), 9 unit quiz-session, 10 unit quiz-view - Docs: alur resume + keterbatasan di 12-quiz-integrity.md, ringkasan di 07-quiz-authoring.md, kontrak endpoint di 02-backend-flask.md
2026-10-20 19:24:40 +07:00
def is_quiz_attempt_resumable(token, lesson_name):
"""True bila attempt terminated siswa di-resume guru (bisa dilanjutkan)."""
return _backend().is_quiz_attempt_resumable(token, lesson_name)
def reset_student_progress(student_id, lesson_name):
"""Reset progress siswa by student_id (id anonim dari report — bukan token)."""
return _backend().reset_progress(student_id, lesson_name)
feat(quiz): izinkan guru melanjutkan attempt terminated dari jawaban tersimpan Kebijakan termination tetap strict, tapi guru pegang override untuk kendala tak terduga (false-positive blur dari dialog OS, browser crash, refresh tak sengaja). Selain reset (siswa mengulang dari nol), guru kini bisa memberi izin resume — siswa melanjutkan dari jawaban yang sudah tersimpan. - Tambah kolom quiz_attempts.resumed_at (migrasi 0006) sebagai penanda izin guru melanjutkan attempt terminated - Tambah endpoint teacher-only POST /resume-quiz-attempt — kontrak keamanan sama dengan /reset-progress (guru cukup student_id anonim, tanpa token siswa) - Syarat: hanya attempt ber-status terminated yang bisa di-resume; submitted ditolak karena kuis sudah selesai normal - has_quiz_attempt meloloskan attempt ber-izin sehingga gate one-attempt terbuka selama sesi lanjutan - finalize_quiz_attempt menimpa row attempt sekali (status/skor/answers baru) lalu clear resumed_at; retry berikutnya kembali ke jalur idempotent normal - Sinyal ke frontend siswa lewat quiz_resumable pada data lesson; laporan guru menampilkan badge "Dilanjutkan" dan tombol "Lanjutkan" untuk attempt terminated - Frontend: restoreQuizSession() merekonstruksi sesi dari jawaban tersimpan per question_id, is_correct dihitung ulang dari source sebagai guard bila konten soal berubah; option_id tak dikenal dianggap belum dijawab - Frontend: firstUnansweredIndex() menentukan soal awal sesi lanjutan; resolveQuizTabView() memusatkan keputusan view agar transisi resume dapat diuji Vitest - UI siswa: panel "Lanjutkan Kuis" + snackbar mode lanjutan (sekali muncul). Attempt di-refetch segar sebelum sesi dimulai agar izin yang sudah dicabut guru tidak memulai sesi yang jawabannya akan terbuang - Test: 16 integrasi backend (services/tests/test_resume_quiz.py), 9 unit quiz-session, 10 unit quiz-view - Docs: alur resume + keterbatasan di 12-quiz-integrity.md, ringkasan di 07-quiz-authoring.md, kontrak endpoint di 02-backend-flask.md
2026-10-20 19:24:40 +07:00
def resume_quiz_attempt(student_id, lesson_name):
"""Beri izin guru melanjutkan attempt terminated siswa (via student_id)."""
return _backend().resume_quiz_attempt(student_id, lesson_name)
def get_all_students_progress(all_lessons_func):
"""(students, ordered_lessons) — tanpa token mentah di student dict."""
return _backend().get_all_students_progress(all_lessons_func)
2026-03-25 09:39:51 +07:00
def calculate_student_completion(student_data, all_lessons):
"""Helper murni bersama (parity kedua backend)."""
from services.storage.completion import calculate_student_completion as _calc
2026-03-25 09:39:51 +07:00
return _calc(student_data, all_lessons)