feat: EnCoach V2 — complete OWL refactor with 15 new modules
Full architectural refactor from React to Odoo OWL: - 15 new Odoo modules: signup, placement, exam_template, scoring, course_gen, entity_onboard, ai_course, quality_gate, ielts_validation, pdf_report, verification, math, it, portal, exam_session - 6 modified modules: core (new user fields), exam (template types), adaptive (events/paths/settings), branding (white-label), resources (CEFR/AI fields), taxonomy (Math+IT hierarchies) - ~79 new REST API endpoints across all controller packages - ~50 admin backend views (form/list/kanban/search/menu) - 5 custom OWL components: dashboard, content pool, exam validation, gap analysis, adaptive timeline - POS-inspired full-screen exam session with 9 question renderers - Student portal with 12 website pages (QWeb + OWL) - AI/ML services: IRT 3PL CAT engine, CEFR mapper, quality gates, IELTS validator, SymPy math scorer, speaking evaluator, adaptive engine - Seed data: IELTS/TOEFL/STEP/IC3 templates, 30+ sample questions, English/Math/IT taxonomy trees with 50+ topics - Updated requirements.txt with new dependencies Made-with: Cursor
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from . import controllers
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
'name': 'EnCoach Verification',
|
||||
'version': '19.0.1.0',
|
||||
'category': 'Education',
|
||||
'summary': 'Public score verification endpoint for QR code-based result authentication',
|
||||
'author': 'EnCoach',
|
||||
'license': 'LGPL-3',
|
||||
'depends': ['encoach_core', 'encoach_scoring'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
from . import verify
|
||||
@@ -0,0 +1,71 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.encoach_api.controllers.base import (
|
||||
_json_response, _error_response,
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EncoachVerifyController(http.Controller):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/verify/<string:hash_code> — public, no auth required
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/verify/<string:hash_code>', type='http',
|
||||
auth='none', methods=['GET'], csrf=False)
|
||||
def verify_result(self, hash_code, **kw):
|
||||
try:
|
||||
env = request.env(su=True)
|
||||
Attempt = env['encoach.student.attempt']
|
||||
Score = env['encoach.score']
|
||||
|
||||
secret = env['ir.config_parameter'].get_param(
|
||||
'encoach.verification_secret', 'default-secret-change-me',
|
||||
)
|
||||
|
||||
attempts = Attempt.search([('status', '=', 'released')])
|
||||
|
||||
for attempt in attempts:
|
||||
student_name = (
|
||||
attempt.student_id.name if attempt.student_id else ''
|
||||
)
|
||||
exam_name = (
|
||||
attempt.exam_id.name if attempt.exam_id else ''
|
||||
)
|
||||
message = f"{attempt.id}:{student_name}:{exam_name}"
|
||||
computed = hmac.new(
|
||||
secret.encode('utf-8'),
|
||||
message.encode('utf-8'),
|
||||
hashlib.sha256,
|
||||
).hexdigest()
|
||||
|
||||
if hmac.compare_digest(computed, hash_code):
|
||||
scores = Score.search([('attempt_id', '=', attempt.id)])
|
||||
skills = []
|
||||
for s in scores:
|
||||
skills.append({
|
||||
'skill': s.skill or '',
|
||||
'band': s.band_score,
|
||||
})
|
||||
|
||||
return _json_response({
|
||||
'verified': True,
|
||||
'student_name': student_name,
|
||||
'exam_name': exam_name,
|
||||
'date': str(attempt.started_at or attempt.create_date or '')[:19],
|
||||
'skills': skills,
|
||||
'overall_band': attempt.overall_band,
|
||||
'cefr_level': attempt.cefr_level or '',
|
||||
})
|
||||
|
||||
return _error_response('Verification hash not found', 404)
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('verify_result failed')
|
||||
return _error_response(str(e), 500)
|
||||
@@ -0,0 +1 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
|
Reference in New Issue
Block a user