Skip to content

Commit a079a0a

Browse files
Abdul MuqadimAbdul Muqadim
authored andcommitted
docs: address review feedback on merge similar endpoints ADR
- Reword "pure REST" alternative per @deborahgu's suggestion: the proposed design is already RESTful (certificate_task is the noun, POST creates the task, payload specifies the task type) - Clarify authorization model: preserve the three distinct permissions from the legacy endpoints via two-layer enforcement (coarse view-level check + per-mode service-level check). Updated implementation requirements, view docstring, and code skeleton to reflect this
1 parent 264c3c2 commit a079a0a

1 file changed

Lines changed: 28 additions & 9 deletions

File tree

docs/decisions/0031-merge-similar-endpoints.rst

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Merge Similar Endpoints
22
=======================
33

4-
:Status: Proposed
4+
:Status: Accepted
55
:Date: 2026-03-31
66
:Deciders: Open edX Platform / API Working Group
77
:Technical Story: Open edX REST API Standards - Consolidation of fragmented same-resource endpoints into unified parameterised views
@@ -49,8 +49,12 @@ Implementation requirements:
4949
applied to that resource.
5050
* Expose a single URL per resource group accepting an ``action`` or ``mode`` field (or using HTTP
5151
verbs semantically where REST conventions apply cleanly).
52-
* Move shared logic — permission checking, input validation, audit logging — into a common service
53-
layer or mixin that all operations invoke.
52+
* Move shared infrastructure, input validation, audit logging, response shaping, and the
53+
enforcement machinery for permissions, into a common service layer or mixin that all operations
54+
invoke. The distinct authorization requirements of the legacy endpoints must be preserved: the
55+
view performs a coarse access check, and each mode handler in the service layer enforces its
56+
own specific permission. Consolidation removes duplicated boilerplate; it does not flatten the
57+
authorization model.
5458
* Preserve backward compatibility via URL aliases or deprecation redirects for a defined transition
5559
window.
5660
* Document the unified endpoint schema in drf-spectacular / OpenAPI, including the enumerated set
@@ -93,19 +97,35 @@ Valid ``mode`` values: ``generate``, ``regenerate``, ``toggle``.
9397
9498
# lms/djangoapps/instructor/views/api.py
9599
class CertificateTaskView(APIView):
96-
"""Unified entry point for certificate generation lifecycle operations."""
100+
"""
101+
Unified entry point for certificate generation lifecycle operations.
102+
103+
Authorization is enforced in two layers:
104+
105+
1. A coarse view-level check confirms the caller has instructor-level
106+
access to the course at all.
107+
2. Per-mode permission checks live inside the corresponding
108+
``CertificateTaskService`` method, preserving the distinct
109+
authorization requirements of the legacy endpoints
110+
(``enable_certificate_generation``,
111+
``start_certificate_generation``,
112+
``start_certificate_regeneration``).
113+
"""
97114
98115
VALID_MODES = {"generate", "regenerate", "toggle"}
99116
100117
def post(self, request, course_id):
101118
course_key = CourseKey.from_string(course_id)
119+
# Coarse authorization: must be an instructor on this course.
102120
_check_instructor_permissions(request.user, course_key)
103121
104122
mode = request.data.get("mode")
105123
if mode not in self.VALID_MODES:
106124
raise ValidationError({"mode": f"Must be one of: {self.VALID_MODES}"})
107125
108-
service = CertificateTaskService(course_key)
126+
service = CertificateTaskService(course_key, request.user)
127+
# Each service method enforces its own mode-specific permission
128+
# before dispatching to the underlying task.
109129
result = getattr(service, mode)(request.data)
110130
return Response(result, status=status.HTTP_200_OK)
111131
@@ -141,10 +161,9 @@ Alternatives Considered
141161

142162
* **Keep per-action endpoints**: Rejected. The duplication cost compounds with every new operation
143163
and makes consistent error handling and logging practically impossible to enforce.
144-
* **Use HTTP verbs exclusively (pure REST)**: Partially applicable — ``POST`` for create,
145-
``DELETE`` for unenroll — but breaks down for operations that do not map cleanly to HTTP verbs
146-
(e.g., ``enable_certificate_generation``). A hybrid approach (HTTP verbs where natural,
147-
``action`` / ``mode`` parameter otherwise) is acceptable.
164+
* **Use HTTP verbs exclusively (pure REST)**: Not applicable. This is already RESTful.
165+
The noun is ``certificate_task``, the ``POST`` indicates that we are creating a
166+
certificate task, and the payload indicates what the task is going to be.
148167
* **GraphQL mutations**: Considered but out of scope for this iteration; the platform's existing
149168
REST ecosystem makes a full GraphQL migration impractical in the near term.
150169

0 commit comments

Comments
 (0)