Skip to content

Commit cef0381

Browse files
committed
Add enrollment verification view
1 parent 54e0402 commit cef0381

11 files changed

Lines changed: 163 additions & 39 deletions

File tree

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from django.urls import path
2-
from django_email_learning.personalised.views import QuizPublicView
2+
from django_email_learning.personalised.views import (
3+
QuizPublicView,
4+
VerifyEnrollmentView,
5+
)
36

47
app_name = "email_learning"
58

69
urlpatterns = [
710
path("quiz/", QuizPublicView.as_view(), name="quiz_public_view"),
811
path(
9-
"verify-enrollment/", QuizPublicView.as_view(), name="verify_enrollment"
10-
), # TODO: Replace with actual view
12+
"verify-enrollment/", VerifyEnrollmentView.as_view(), name="verify_enrollment"
13+
),
1114
]

django_email_learning/personalised/views.py

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,32 @@
55
from django_email_learning.models import ContentDelivery, EnrollmentStatus
66
from django_email_learning.services import jwt_service
77
from django_email_learning.personalised.serializers import PublicQuizSerializer
8+
from django_email_learning.services.command_models.verify_enrollment_command import (
9+
VerifyEnrollmentCommand,
10+
)
811
import uuid
912
import logging
1013

1114

12-
class QuizPublicView(View, TemplateResponseMixin):
15+
class ErrrorLoggingMixin(TemplateResponseMixin):
16+
def errr_response(
17+
self, message: str, exception: Exception | None, status_code: int = 500
18+
) -> HttpResponse:
19+
error_ref = uuid.uuid4().hex
20+
if exception:
21+
logging.exception(
22+
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
23+
)
24+
else:
25+
logging.error(
26+
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
27+
)
28+
return self.render_to_response(
29+
context={"ref": error_ref, "error_message": message}, status=status_code
30+
)
31+
32+
33+
class QuizPublicView(View, ErrrorLoggingMixin):
1334
template_name = "personalised/quiz_public.html"
1435

1536
def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-untyped-def]
@@ -69,18 +90,47 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
6990
message="The link has expired", exception=e, status_code=410
7091
)
7192

72-
def errr_response(
73-
self, message: str, exception: Exception | None, status_code: int = 500
74-
) -> HttpResponse:
75-
error_ref = uuid.uuid4().hex
76-
if exception:
77-
logging.exception(
78-
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
93+
94+
class VerifyEnrollmentView(View, ErrrorLoggingMixin):
95+
template_name = "personalised/verify_enrollment.html"
96+
97+
def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-untyped-def]
98+
try:
99+
token = request.GET["token"]
100+
except KeyError as e:
101+
return self.errr_response(
102+
message="The verification link is not valid.",
103+
exception=e,
104+
status_code=400,
79105
)
80-
else:
81-
logging.error(
82-
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
106+
try:
107+
decoded = jwt_service.decode_jwt(token=token)
108+
except jwt_service.InvalidTokenException as e:
109+
return self.errr_response(
110+
message="The verification link is not valid.",
111+
exception=e,
112+
status_code=400,
83113
)
84-
return self.render_to_response(
85-
context={"ref": error_ref, "error_message": message}, status=status_code
114+
except jwt_service.ExpiredTokenException as e:
115+
return self.errr_response(
116+
message="The verification link has expired.",
117+
exception=e,
118+
status_code=410,
119+
)
120+
121+
enrollment_id = decoded["enrollment_id"]
122+
verification_code = decoded["verification_code"]
123+
124+
command = VerifyEnrollmentCommand(
125+
command_name="verify_enrollment",
126+
enrollment_id=enrollment_id,
127+
verification_code=verification_code,
86128
)
129+
try:
130+
command.execute()
131+
except Exception as e:
132+
return self.errr_response(
133+
message="An error occurred during enrollment verification.", exception=e
134+
)
135+
136+
return self.render_to_response(context={"page_title": "Enrollment Verified"})

django_email_learning/services/command_models/verify_enrollment_command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def execute(self) -> None:
2929
self.logger.error(
3030
f"Verification Failed: No unverified enrollment found with ID {self.enrollment_id}"
3131
)
32+
# Check if enrollment exists but is not unverified
33+
if Enrollment.objects.filter(id=self.enrollment_id).exists():
34+
return
3235
raise InvalidEnrollmentError(
3336
f"No unverified enrollment found with ID {self.enrollment_id}"
3437
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% load django_vite %}
2+
{% load static %}
3+
<!doctype html>
4+
<html lang="en">
5+
<head>
6+
<script>
7+
const error_message = "{{ error_message }}";
8+
const ref = "{{ ref }}";
9+
</script>
10+
{% vite_hmr_client %}
11+
{% vite_react_refresh %}
12+
{% block head_script %}{% endblock %}
13+
<meta charset="UTF-8" />
14+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
15+
<link rel="icon" type="image/png" href="{% static 'logo.png' %}" />
16+
<title>{% block title %}{{ page_title }}{% endblock %}</title>
17+
</head>
18+
<body>
19+
<div id="root">
20+
</div>
21+
</body>
22+
</html>
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1+
{% extends "personalised/base.html" %}
12
{% load django_vite %}
2-
{% load static %}
3-
<!doctype html>
4-
<html lang="en">
5-
<head>
6-
{% vite_hmr_client %}
7-
{% vite_react_refresh %}
3+
{% block head_script %}
84
<script>
95
let quiz = {{ quiz|default:"null"|safe }};
106
let token = "{{ token }}";
@@ -14,13 +10,4 @@
1410
let ref = "{{ ref }}";
1511
</script>
1612
{% vite_asset 'my/quiz/Quiz.jsx' %}
17-
<meta charset="UTF-8" />
18-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
19-
<link rel="icon" type="image/png" href="{% static 'logo.png' %}" />
20-
<title>{% block title %}{{ page_title }}{% endblock %}</title>
21-
</head>
22-
<body>
23-
<div id="root">
24-
</div>
25-
</body>
26-
</html>
13+
{% endblock %}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% extends "personalised/base.html" %}
2+
{% load django_vite %}
3+
{% block head_script %}
4+
{% vite_asset 'my/verification/Verification.jsx' %}
5+
{% endblock %}

frontend/my/quiz/Quiz.jsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ const Quiz = () => {
1414
const [isPassed, setIsPassed] = useState(null);
1515
const [score, setScore] = useState(null);
1616

17-
if (error_message) {
18-
console.log("Error:", error_message, ref);
19-
} else {
20-
console.log("No error");
21-
}
22-
2317
const showSubmitDialog = () => {
2418
setDialogOpen(true);
2519
let answerCounter = 0;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Alert, Box } from '@mui/material';
2+
import render from '../../src/render.jsx';
3+
4+
5+
const Verification = () => {
6+
return <>
7+
{ !error_message ?<Alert severity='success' textAlign="left" sx={{ maxWidth: 800, margin: '0 auto', backgroundColor: "background.light" }}>
8+
You have successfully verified your enrollment.
9+
</Alert> : <Alert severity="error" sx={{ maxWidth: 800, margin: '20px auto' }}>
10+
{error_message} (ref: {ref})
11+
</Alert>}
12+
</>
13+
}
14+
15+
render({children: <Verification />});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Verification</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="Verification.jsx"></script>
11+
</body>
12+
</html>

frontend/vite.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export default defineConfig({
2929
course: resolve(__dirname, 'course/index.html'),
3030
organizations: resolve(__dirname, 'organizations/index.html'),
3131
users: resolve(__dirname, 'users/index.html'),
32-
quiz: resolve(__dirname, "my/index.html")
32+
quiz: resolve(__dirname, "my/quiz/index.html"),
33+
verification: resolve(__dirname, "my/verification/index.html"),
3334
}
3435
},
3536
manifest: 'manifest.json',

0 commit comments

Comments
 (0)