-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REFERENCE] feat: introduce Gateway Aggregation / Backend-for-Frontend abstraction via LearnerPortalBFFAPIView #572
Draft
adamstankiewicz
wants to merge
3
commits into
main
Choose a base branch
from
ags/poc-learner-portal-bff
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ | |
SubsidyAccessPolicyRedeemViewset, | ||
SubsidyAccessPolicyViewSet | ||
) | ||
from .bffs import LearnerPortalBFFAPIView |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
""" | ||
Enterprise BFFs for MFEs. | ||
""" | ||
|
||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.authentication import get_authorization_header, SessionAuthentication | ||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication | ||
from edx_rest_framework_extensions.auth.jwt.cookies import jwt_cookie_name | ||
|
||
from enterprise_access.apps.bffs.context import HandlerContext | ||
from enterprise_access.apps.bffs.handlers import LearnerPortalHandlerFactory | ||
from enterprise_access.apps.bffs.response_builder import LearnerPortalResponseBuilderFactory | ||
|
||
|
||
class LearnerPortalBFFAPIView(APIView): | ||
""" | ||
API view for learner portal BFF routes. | ||
""" | ||
|
||
authentication_classes = [JwtAuthentication] | ||
permission_classes = [IsAuthenticated] | ||
|
||
def post(self, request, page_route, *args, **kwargs): | ||
""" | ||
Handles GET requests for learner-specific routes. | ||
|
||
Args: | ||
request (Request): The request object. | ||
route (str): The specific learner portal route (e.g., 'dashboard'). | ||
|
||
Returns: | ||
Response: The response data formatted by the response builder. | ||
""" | ||
|
||
# Create the context based on the request | ||
context = HandlerContext(page_route=page_route, request=request) | ||
|
||
# Use the LearnerPortalResponseBuilderFactory to get the appropriate response builder | ||
response_builder = LearnerPortalResponseBuilderFactory.get_response_builder(context) | ||
|
||
try: | ||
# Use the LearnerHandlerFactory to get the appropriate handler | ||
handler = LearnerPortalHandlerFactory.get_handler(context) | ||
|
||
# Load and process data using the handler | ||
handler.load_and_process() | ||
except Exception as exc: | ||
context.add_error( | ||
user_message="An error occurred while processing the request.", | ||
developer_message=f"Error: {exc}", | ||
severity="error", | ||
) | ||
|
||
# Build the response data and status code | ||
response_data, status_code = response_builder.build() | ||
|
||
return Response(response_data, status=status_code) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import crum | ||
import requests | ||
|
||
from edx_django_utils.monitoring import set_custom_attribute | ||
from edx_rest_framework_extensions.auth.jwt.cookies import jwt_cookie_name | ||
|
||
|
||
def get_request_id(): | ||
""" | ||
Helper to get the request id - usually set via an X-Request-ID header | ||
""" | ||
request = crum.get_current_request() | ||
if request is not None and request.headers is not None: | ||
return request.headers.get('X-Request-ID') | ||
else: | ||
return None | ||
|
||
|
||
class BaseUserApiClient(requests.Session): | ||
""" | ||
A requests Session that includes the Authorization and User-Agent headers from the original request. | ||
""" | ||
def __init__(self, original_request, **kwargs): | ||
super().__init__(**kwargs) | ||
self.original_request = original_request | ||
|
||
self.headers = {} | ||
|
||
if self.original_request: | ||
# If Authorization header is present in the original request, pass through to subsequent request headers | ||
if 'Authorization' in self.original_request.headers: | ||
self.headers['Authorization'] = self.original_request.headers['Authorization'] | ||
|
||
# If no Authorization header, check for JWT in cookies | ||
jwt_token = self.original_request.COOKIES.get(jwt_cookie_name()) | ||
if 'Authorization' not in self.headers and jwt_token is not None: | ||
self.headers['Authorization'] = f'JWT {jwt_token}' | ||
|
||
# Add X-Request-ID header if applicable | ||
request_id = get_request_id() | ||
if self.headers.get('X-Request-ID') is None and request_id is not None: | ||
self.headers['X-Request-ID'] = request_id | ||
|
||
def request(self, method, url, headers=None, **kwargs): # pylint: disable=arguments-differ | ||
if headers: | ||
headers.update(self.headers) | ||
else: | ||
headers = self.headers | ||
|
||
# Set `api_client` as a custom attribute for monitoring, reflecting the API client's module path | ||
set_custom_attribute('api_client', 'enterprise_access.apps.api_client.base_user.BaseUserApiClient') | ||
|
||
return super().request(method, url, headers=headers, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class BffsConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'enterprise_access.apps.bffs' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
""" | ||
HandlerContext for bffs app. | ||
""" | ||
|
||
class HandlerContext: | ||
""" | ||
A context object for managing the state throughout the lifecycle of a Backend-for-Frontend (BFF) request. | ||
|
||
The `HandlerContext` class stores request information, the current route, loaded data, and any errors | ||
that may occur during the request. | ||
|
||
Attributes: | ||
request: The original request object containing information about the incoming HTTP request. | ||
route: The route for which the response is being generated. | ||
data: A dictionary to store data loaded and processed by the handlers. | ||
errors: A list to store errors that occur during request processing. | ||
""" | ||
|
||
def __init__(self, request, page_route): | ||
""" | ||
Initializes the HandlerContext with request information, route, and optional initial data. | ||
|
||
Args: | ||
request: The incoming HTTP request. | ||
page_route: The route identifier for the request. | ||
""" | ||
self.page_route = page_route | ||
self.request = request | ||
self.user = request.user | ||
self.data = {} # Stores processed data for the response | ||
self.errors = [] # Stores any errors that occur during processing | ||
self.enterprise_customer_uuid = None | ||
self.lms_user_id = None | ||
|
||
def add_error(self, user_message, developer_message, severity='error'): | ||
""" | ||
Adds an error to the context. | ||
|
||
Args: | ||
user_message (str): A user-friendly error message. | ||
developer_message (str): A more detailed error message for debugging purposes. | ||
severity (str): The severity level of the error ('error' or 'warning'). Defaults to 'error'. | ||
""" | ||
if not (user_message and developer_message): | ||
raise ValueError("User message and developer message are required for errors.") | ||
|
||
self.errors.append({ | ||
"user_message": user_message, | ||
"developer_message": developer_message, | ||
"severity": severity, | ||
}) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[inform] Replicated from here