feat: QA fixes, new APIs (assignments, rubrics, custom exams), Generation page enhancements
- Fix ELAI video generation (correct render endpoint, script splitting for 60s limit) - Fix speaking script generation error handling and empty response display - Add custom exam list API (GET /api/exam/custom/list) - Add assignments REST API (list, create, get) - Add rubrics REST API (list, create) - Enhance Generation page: dynamic exam structures, auto-module selection, preview dialog, audio player - Improve submit feedback with exam ID and status in toast notifications - Fix ExamsListPage to show both custom exams and exam sessions - Connect RubricsPage to backend API with fallback data - Add Dockerfile, docker-compose.yml, requirements.txt for deployment - Fix placement, grading, scoring, and auth controllers - Add ErrorBoundary component for frontend resilience - Add QA report and credentials documentation Made-with: Cursor
This commit is contained in:
@@ -101,6 +101,24 @@ class EncoachSignupController(http.Controller):
|
||||
|
||||
_logger.info('Registration OTP for %s: %s', email, otp_code)
|
||||
|
||||
try:
|
||||
template = request.env.ref('encoach_signup.email_template_otp', raise_if_not_found=False)
|
||||
if template:
|
||||
template.sudo().with_context(otp_code=otp_code).send_mail(user.id, force_send=True)
|
||||
else:
|
||||
mail_values = {
|
||||
'subject': 'EnCoach - Verify Your Email',
|
||||
'email_from': request.env['ir.config_parameter'].sudo().get_param(
|
||||
'mail.catchall.email', 'noreply@encoach.com'),
|
||||
'email_to': email,
|
||||
'body_html': f'<p>Welcome to EnCoach!</p>'
|
||||
f'<p>Your verification code is: <strong>{otp_code}</strong></p>'
|
||||
f'<p>This code expires in 15 minutes.</p>',
|
||||
}
|
||||
request.env['mail.mail'].sudo().create(mail_values).send()
|
||||
except Exception as mail_err:
|
||||
_logger.warning('OTP email failed for %s: %s', email, mail_err)
|
||||
|
||||
return _json_response({
|
||||
'message': 'Registration successful. Please verify your email.',
|
||||
'user_id': user.id,
|
||||
@@ -205,12 +223,116 @@ class EncoachSignupController(http.Controller):
|
||||
|
||||
_logger.info('Resend OTP for %s: %s', email, otp_code)
|
||||
|
||||
try:
|
||||
user = request.env['res.users'].sudo().search(
|
||||
[('login', '=', email)], limit=1)
|
||||
if user:
|
||||
mail_values = {
|
||||
'subject': 'EnCoach - Your New Verification Code',
|
||||
'email_from': request.env['ir.config_parameter'].sudo().get_param(
|
||||
'mail.catchall.email', 'noreply@encoach.com'),
|
||||
'email_to': email,
|
||||
'body_html': f'<p>Your new verification code is: <strong>{otp_code}</strong></p>'
|
||||
f'<p>This code expires in 15 minutes.</p>',
|
||||
}
|
||||
request.env['mail.mail'].sudo().create(mail_values).send()
|
||||
except Exception as mail_err:
|
||||
_logger.warning('Resend OTP email failed for %s: %s', email, mail_err)
|
||||
|
||||
return _json_response({'message': 'OTP resent'})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('resend_otp failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/reset/sendVerification
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/reset/sendVerification', type='http', auth='public',
|
||||
methods=['POST'], csrf=False)
|
||||
def reset_send_verification(self, **kw):
|
||||
try:
|
||||
body = _get_json_body()
|
||||
email = (body.get('email') or '').strip().lower()
|
||||
if not email:
|
||||
return _error_response('email is required', 400)
|
||||
|
||||
user = request.env['res.users'].sudo().search(
|
||||
[('login', '=', email)], limit=1)
|
||||
if not user:
|
||||
return _json_response({'message': 'If the email exists, a reset code has been sent.'})
|
||||
|
||||
otp_code = ''.join(random.choices(string.digits, k=6))
|
||||
otp_hash = hashlib.sha256(otp_code.encode()).hexdigest()
|
||||
expires_at = fields.Datetime.now() + timedelta(minutes=15)
|
||||
|
||||
request.env['encoach.otp'].sudo().create({
|
||||
'email': email,
|
||||
'otp_hash': otp_hash,
|
||||
'expires_at': expires_at,
|
||||
})
|
||||
|
||||
try:
|
||||
template = request.env.ref('encoach_signup.email_template_password_reset', raise_if_not_found=False)
|
||||
if template:
|
||||
template.sudo().with_context(otp_code=otp_code).send_mail(user.id, force_send=True)
|
||||
else:
|
||||
mail_values = {
|
||||
'subject': 'EnCoach Password Reset Code',
|
||||
'email_from': request.env['ir.config_parameter'].sudo().get_param(
|
||||
'mail.catchall.email', 'noreply@encoach.com'),
|
||||
'email_to': email,
|
||||
'body_html': f'<p>Your password reset code is: <strong>{otp_code}</strong></p>'
|
||||
f'<p>This code expires in 15 minutes.</p>',
|
||||
}
|
||||
request.env['mail.mail'].sudo().create(mail_values).send()
|
||||
except Exception as mail_err:
|
||||
_logger.warning('Password reset email failed for %s: %s', email, mail_err)
|
||||
|
||||
_logger.info('Password reset OTP for %s: %s', email, otp_code)
|
||||
return _json_response({'message': 'If the email exists, a reset code has been sent.'})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('reset_send_verification failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/auth/invite/set-password
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/auth/invite/set-password', type='http', auth='public',
|
||||
methods=['POST'], csrf=False)
|
||||
def invite_set_password(self, **kw):
|
||||
try:
|
||||
body = _get_json_body()
|
||||
token = body.get('token', '').strip()
|
||||
password = body.get('password', '')
|
||||
|
||||
if not token or not password:
|
||||
return _error_response('token and password are required', 400)
|
||||
|
||||
Invite = request.env['encoach.invite'].sudo()
|
||||
invite = Invite.search([('token', '=', token), ('used', '=', False)], limit=1)
|
||||
|
||||
if not invite:
|
||||
return _error_response('Invalid or expired invitation token', 400)
|
||||
|
||||
user = invite.user_id
|
||||
if not user:
|
||||
return _error_response('No user associated with this invitation', 400)
|
||||
|
||||
user.sudo().write({'password': password})
|
||||
invite.write({'used': True})
|
||||
user.sudo().write({
|
||||
'account_status': 'activated',
|
||||
'first_login': False,
|
||||
})
|
||||
|
||||
return _json_response({'message': 'Password set successfully'})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('invite_set_password failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/onboarding/goals
|
||||
# ------------------------------------------------------------------
|
||||
@@ -342,3 +464,32 @@ class EncoachSignupController(http.Controller):
|
||||
except Exception as e:
|
||||
_logger.exception('captcha_config failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/subscription/trial
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/subscription/trial', type='http', auth='none',
|
||||
methods=['POST'], csrf=False)
|
||||
@jwt_required
|
||||
def start_trial(self, **kw):
|
||||
try:
|
||||
user = request.env.user
|
||||
ICP = request.env['ir.config_parameter'].sudo()
|
||||
trial_days = int(ICP.get_param('encoach.trial_duration_days', '14'))
|
||||
|
||||
from datetime import timedelta
|
||||
trial_end = fields.Datetime.now() + timedelta(days=trial_days)
|
||||
|
||||
user.sudo().write({
|
||||
'account_status': 'trial',
|
||||
})
|
||||
|
||||
return _json_response({
|
||||
'message': f'Trial activated for {trial_days} days',
|
||||
'trial_end': str(trial_end),
|
||||
'status': 'trial',
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('start_trial failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
Reference in New Issue
Block a user