feat: initial backend codebase — EnCoach v3

Complete Odoo 19 backend with 25 custom addons:
- encoach_core: user/entity/role management
- encoach_api: REST API + JWT auth
- encoach_ai: OpenAI integration, AI settings, generation
- encoach_ai_course: AI-powered English & IELTS course generation
- encoach_exam_template/session: exam creation, structures, sessions
- encoach_scoring: AI auto-grading + manual approval
- encoach_vector: pgvector RAG integration
- encoach_adaptive: adaptive learning engine
- encoach_placement: placement testing
- encoach_taxonomy/resources: content taxonomy & resource management
- Plus 14 more modules for courses, branding, portal, etc.

Includes docs: user guide, generation report, developer workflow.

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-11 15:44:20 +04:00
commit 982d4bca30
371 changed files with 35211 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import controllers

View File

@@ -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,
}

View File

@@ -0,0 +1 @@
from . import verify

View File

@@ -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)

View File

@@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink