-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #116 from jadmsaadaot/SUBMIT-task#100
setup common hosted email service
- Loading branch information
Showing
6 changed files
with
209 additions
and
0 deletions.
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
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,50 @@ | ||
# Copyright © 2024 Province of British Columbia | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the 'License'); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an 'AS IS' BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""API endpoints for managing an email resource.""" | ||
|
||
from http import HTTPStatus | ||
|
||
from flask_restx import Namespace, Resource, cors | ||
|
||
from submit_api.utils.util import cors_preflight | ||
|
||
from ..auth import auth | ||
from .apihelper import Api as ApiHelper | ||
from ..services.ches_service import ChesApiService, EmailDetails | ||
|
||
API = Namespace("email", description="Endpoints for sending emails") | ||
"""Custom exception messages | ||
""" | ||
|
||
|
||
@cors_preflight("POST, OPTIONS") | ||
@API.route("", methods=["POST", "OPTIONS"]) | ||
class Item(Resource): | ||
"""Resource for managing projects.""" | ||
|
||
@staticmethod | ||
@ApiHelper.swagger_decorators( | ||
API, endpoint_description="send email" | ||
) | ||
@API.response(HTTPStatus.BAD_REQUEST, "Bad Request") | ||
@cors.crossdomain(origin="*") | ||
@auth.require | ||
def post(): | ||
"""Send email.""" | ||
request_json = API.payload | ||
email_details = EmailDetails( | ||
**request_json | ||
) | ||
response_json, status = ChesApiService().send_email(email_details) | ||
return response_json, status |
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,123 @@ | ||
# Copyright © 2019 Province of British Columbia | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the 'License'); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an 'AS IS' BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
"""Service for integrating with the Common Hosted Email Service.""" | ||
import base64 | ||
import json | ||
from datetime import datetime, timedelta | ||
from typing import Optional, List | ||
import requests | ||
from attr import dataclass | ||
from flask import current_app | ||
from submit_api.utils.template import Template | ||
|
||
|
||
@dataclass | ||
class EmailDetails: | ||
"""Email details class.""" | ||
|
||
sender: str | ||
recipients: List[str] | ||
subject: str | ||
body: Optional[str] = None | ||
template_name: Optional[str] = None | ||
body_args: Optional[dict] = None | ||
cc: Optional[List[str]] = None | ||
bcc: Optional[List[str]] = None | ||
|
||
def __post_init__(self): | ||
"""Post init method to initialize optional fields.""" | ||
self.body_args = self.body_args or {} | ||
self.cc = self.cc or [] # pylint: disable=invalid-name | ||
self.bcc = self.bcc or [] | ||
|
||
|
||
class ChesApiService: | ||
"""CHES api Service class.""" | ||
|
||
def __init__(self): | ||
"""Initiate class.""" | ||
self.token_endpoint = current_app.config.get('CHES_TOKEN_ENDPOINT') | ||
self.service_client_id = current_app.config.get('CHES_CLIENT_ID') | ||
self.service_client_secret = current_app.config.get('CHES_CLIENT_SECRET') | ||
self.ches_base_url = current_app.config.get('CHES_BASE_URL') | ||
self.access_token, self.token_expiry = self._get_access_token() | ||
|
||
def _get_access_token(self): | ||
basic_auth_encoded = base64.b64encode( | ||
bytes(f'{self.service_client_id}:{self.service_client_secret}', 'utf-8') | ||
).decode('utf-8') | ||
data = 'grant_type=client_credentials' | ||
response = requests.post( | ||
self.token_endpoint, | ||
data=data, | ||
headers={ | ||
'Authorization': f'Basic {basic_auth_encoded}', | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
}, | ||
timeout=10 | ||
) | ||
response.raise_for_status() | ||
response_json = response.json() | ||
expires_in = response_json['expires_in'] | ||
expiry_time = datetime.now() + timedelta(seconds=expires_in) | ||
return response_json['access_token'], expiry_time | ||
|
||
def _ensure_valid_token(self): | ||
if datetime.now() >= self.token_expiry: | ||
self.access_token, self.token_expiry = self._get_access_token() | ||
|
||
@staticmethod | ||
def _get_email_body_from_template(template_name: str, body_args: dict): | ||
if not template_name: | ||
raise ValueError('Template name is required') | ||
template = Template.get_template(template_name) | ||
if not template: | ||
raise ValueError('Template not found') | ||
return template.render(body_args) | ||
|
||
def _get_email_body(self, email_details: EmailDetails): | ||
if email_details.body: | ||
body = email_details.body | ||
body_type = 'text' | ||
else: | ||
body = self._get_email_body_from_template(email_details.template_name, email_details.body_args) | ||
body_type = 'html' | ||
return body, body_type | ||
|
||
def send_email(self, email_details: EmailDetails): | ||
"""Generate document based on template and data.""" | ||
self._ensure_valid_token() | ||
|
||
body, body_type = self._get_email_body(email_details) | ||
request_body = { | ||
'bodyType': body_type, | ||
'body': body, | ||
'subject': email_details.subject, | ||
'from': email_details.sender, | ||
'to': email_details.recipients, | ||
'cc': email_details.cc, | ||
'bcc': email_details.bcc | ||
} | ||
json_request_body = json.dumps(request_body) | ||
headers = { | ||
'Content-Type': 'application/json', | ||
'Authorization': f'Bearer {self.access_token}' | ||
} | ||
url = f'{self.ches_base_url}/api/v1/email' | ||
response = requests.post(url, data=json_request_body, headers=headers, | ||
timeout=10) | ||
response.raise_for_status() | ||
return response.json(), response.status_code |
13 changes: 13 additions & 0 deletions
13
submit-api/src/submit_api/templates/management_plan_submission_verification.html
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,13 @@ | ||
<p>Hello {{ name }},</p> | ||
<p>Thank you for submitting the management plan on behalf of {{ certificate_holder_name }} through our new submission tool. We have successfully received your submission, and the details are as follows:</p> | ||
<p><strong>Management Plan Name:</strong> {{ management_plan_name }}</p> | ||
<p><strong>Submission Date:</strong> {{ submission_date }}</p> | ||
<p><strong>Submitted Documents:</strong></p> | ||
<ul> | ||
{% for document in documents %} | ||
<li>{{ document }}</li> | ||
{% endfor %} | ||
</ul> | ||
<p>If you have any questions or require further assistance, please don't hesitate to contact us.</p> | ||
<p>Best regards,</p> | ||
<p>EAO Management Plan Team</p> |
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,16 @@ | ||
"""Template Services.""" | ||
|
||
import os | ||
from jinja2 import Environment, FileSystemLoader | ||
|
||
templates_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..', 'templates')) | ||
ENV = Environment(loader=FileSystemLoader(templates_dir), autoescape=True) | ||
|
||
|
||
class Template: | ||
"""Template helper class.""" | ||
|
||
@staticmethod | ||
def get_template(template_filename): | ||
"""Get a template from the common template folder.""" | ||
return ENV.get_template(template_filename) |