- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
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)
|