diff --git a/met-api/src/met_api/config.py b/met-api/src/met_api/config.py index 0fc832917..dc9be82eb 100644 --- a/met-api/src/met_api/config.py +++ b/met-api/src/met_api/config.py @@ -163,9 +163,7 @@ class _Config(): # pylint: disable=too-few-public-methods 'VERIFICATION_EMAIL_TEMPLATE_ID': os.getenv('VERIFICATION_EMAIL_TEMPLATE_ID'), 'VERIFICATION_EMAIL_SUBJECT': os.getenv('VERIFICATION_EMAIL_SUBJECT', '{engagement_name} - Survey link'), 'SUBSCRIBE_EMAIL_TEMPLATE_ID': os.getenv('SUBSCRIBE_EMAIL_TEMPLATE_ID'), - 'SUBSCRIBE_EMAIL_SUBJECT': os.getenv( - 'SUBSCRIBE_EMAIL_SUBJECT', - 'Confirm your Subscription to {engagement_name}'), + 'SUBSCRIBE_EMAIL_SUBJECT': os.getenv('SUBSCRIBE_EMAIL_SUBJECT', 'Confirm your subscription'), 'REJECTED_EMAIL_TEMPLATE_ID': os.getenv('REJECTED_EMAIL_TEMPLATE_ID'), 'REJECTED_EMAIL_SUBJECT': os.getenv('REJECTED_EMAIL_SUBJECT', '{engagement_name} - About your Comments'), 'SUBMISSION_RESPONSE_EMAIL_TEMPLATE_ID': os.getenv('SUBMISSION_RESPONSE_EMAIL_TEMPLATE_ID'), diff --git a/met-api/src/met_api/constants/subscription_type.py b/met-api/src/met_api/constants/subscription_type.py index 7dcae93c5..3abb45f58 100644 --- a/met-api/src/met_api/constants/subscription_type.py +++ b/met-api/src/met_api/constants/subscription_type.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Constants for subscription.""" -from enum import IntEnum +from enum import Enum, IntEnum class SubscriptionType(IntEnum): @@ -28,3 +28,11 @@ class SubscriptionType(IntEnum): ENGAGEMENT = 1 PROJECT = 2 TENANT = 3 + + +class SubscriptionTypes(Enum): + """Enum of Subscription Type.""" + + ENGAGEMENT = 'ENGAGEMENT' + PROJECT = 'PROJECT' + TENANT = 'TENANT' diff --git a/met-api/src/met_api/models/engagement.py b/met-api/src/met_api/models/engagement.py index f59a94ad4..a4375ff4a 100644 --- a/met-api/src/met_api/models/engagement.py +++ b/met-api/src/met_api/models/engagement.py @@ -5,7 +5,7 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, timedelta from typing import List, Optional from sqlalchemy import and_, asc, desc, or_ @@ -314,3 +314,19 @@ def get_assigned_engagements(user_id: int) -> List[Engagement]: )) \ .all() return engagements + + @classmethod + def get_engagements_closing_soon(cls) -> List[Engagement]: + """Get engagements that are closing within two days.""" + now = local_datetime() + two_days_from_now = now + timedelta(days=2) + # Strip the time off the datetime object + date_due = datetime(two_days_from_now.year, two_days_from_now.month, two_days_from_now.day) + engagements = db.session.query(Engagement) \ + .filter( + and_( + Engagement.status_id == Status.Published.value, + Engagement.end_date == date_due + )) \ + .all() + return engagements diff --git a/met-api/src/met_api/resources/email_verification.py b/met-api/src/met_api/resources/email_verification.py index e2b5f7223..d01ab40a2 100644 --- a/met-api/src/met_api/resources/email_verification.py +++ b/met-api/src/met_api/resources/email_verification.py @@ -85,3 +85,24 @@ def post(): return str(err), HTTPStatus.INTERNAL_SERVER_ERROR except ValueError as err: return str(err), HTTPStatus.INTERNAL_SERVER_ERROR + + +@cors_preflight('POST, OPTIONS') +@API.route('//subscribe') +class SubscribeEmailVerifications(Resource): + """Resource for managing email verifications.""" + + @staticmethod + # @TRACER.trace() + @cross_origin(origins=allowedorigins()) + def post(subscription_type): + """Create a new email verification.""" + try: + requestjson = request.get_json() + email_verification = EmailVerificationSchema().load(requestjson) + created_email_verification = EmailVerificationService().create(email_verification, subscription_type) + return created_email_verification, HTTPStatus.OK + except KeyError as err: + return str(err), HTTPStatus.INTERNAL_SERVER_ERROR + except ValueError as err: + return str(err), HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/src/met_api/services/email_verification_service.py b/met-api/src/met_api/services/email_verification_service.py index d2d9d9830..f848eae52 100644 --- a/met-api/src/met_api/services/email_verification_service.py +++ b/met-api/src/met_api/services/email_verification_service.py @@ -6,12 +6,14 @@ from flask import current_app from met_api.constants.email_verification import INTERNAL_EMAIL_DOMAIN, EmailVerificationType +from met_api.constants.subscription_type import SubscriptionTypes from met_api.exceptions.business_exception import BusinessException from met_api.models import Engagement as EngagementModel from met_api.models import EngagementSlug as EngagementSlugModel from met_api.models import Survey as SurveyModel from met_api.models import Tenant as TenantModel from met_api.models.email_verification import EmailVerification +from met_api.models.engagement_metadata import EngagementMetadataModel from met_api.schemas.email_verification import EmailVerificationSchema from met_api.services.participant_service import ParticipantService from met_api.utils import notification @@ -42,7 +44,8 @@ def get_active(cls, verification_token): return email_verification @classmethod - def create(cls, email_verification: EmailVerificationSchema, session=None) -> EmailVerificationSchema: + def create(cls, email_verification: EmailVerificationSchema, + subscription_type='', session=None) -> EmailVerificationSchema: """Create an email verification.""" cls.validate_fields(email_verification) email_address: str = email_verification.get('email_address') @@ -66,7 +69,7 @@ def create(cls, email_verification: EmailVerificationSchema, session=None) -> Em # TODO: remove this once email logic is brought over from submission service to here if email_verification.get('type', None) != EmailVerificationType.RejectedComment: - cls._send_verification_email(email_verification) + cls._send_verification_email(email_verification, subscription_type) return email_verification @@ -93,11 +96,10 @@ def verify(cls, verification_token, survey_id, submission_id, session): return email_verification @staticmethod - def _send_verification_email(email_verification: dict) -> None: + def _send_verification_email(email_verification: dict, subscription_type) -> None: """Send an verification email.Throws error if fails.""" survey_id = email_verification.get('survey_id') email_to = email_verification.get('email_address') - participant_id = email_verification.get('participant_id') survey: SurveyModel = SurveyModel.get_open(survey_id) if not survey: @@ -106,7 +108,11 @@ def _send_verification_email(email_verification: dict) -> None: raise ValueError('Engagement not found') subject, body, args, template_id = EmailVerificationService._render_email_template( - survey, email_verification.get('verification_token'), email_verification.get('type'), participant_id) + survey, + email_verification.get('verification_token'), + email_verification.get('type'), + subscription_type + ) try: # user hasn't been created yet.so create token using SA. notification.send_email( @@ -119,9 +125,12 @@ def _send_verification_email(email_verification: dict) -> None: status_code=HTTPStatus.INTERNAL_SERVER_ERROR) from exc @staticmethod - def _render_email_template(survey: SurveyModel, token, email_type: EmailVerificationType, participant_id): + def _render_email_template(survey: SurveyModel, + token, + email_type: EmailVerificationType, + subscription_type): if email_type == EmailVerificationType.Subscribe: - return EmailVerificationService._render_subscribe_email_template(survey, token, participant_id) + return EmailVerificationService._render_subscribe_email_template(survey, token, subscription_type) # if email_type == EmailVerificationType.RejectedComment: # TODO: move reject comment email verification logic here # return @@ -129,39 +138,39 @@ def _render_email_template(survey: SurveyModel, token, email_type: EmailVerifica @staticmethod # pylint: disable-msg=too-many-locals - def _render_subscribe_email_template(survey: SurveyModel, token, participant_id): + def _render_subscribe_email_template(survey: SurveyModel, token, subscription_type): # url is origin url excluding context path engagement: EngagementModel = EngagementModel.find_by_id( survey.engagement_id) - engagement_name = engagement.name + tenant_name = EmailVerificationService._get_tenant_name( + engagement.tenant_id) + project_name = EmailVerificationService._get_project_name( + subscription_type, tenant_name, engagement) + is_subscribing_to_tenant = subscription_type == SubscriptionTypes.TENANT.value + is_subscribing_to_project = subscription_type != SubscriptionTypes.TENANT.value template_id = get_gc_notify_config('SUBSCRIBE_EMAIL_TEMPLATE_ID') template = Template.get_template('subscribe_email.html') - subject_template = get_gc_notify_config('SUBSCRIBE_EMAIL_SUBJECT') confirm_path = current_app.config.get('SUBSCRIBE_PATH'). \ format(engagement_id=engagement.id, token=token) - unsubscribe_path = current_app.config.get('UNSUBSCRIBE_PATH'). \ - format(engagement_id=engagement.id, participant_id=participant_id) confirm_url = notification.get_tenant_site_url( engagement.tenant_id, confirm_path) - unsubscribe_url = notification.get_tenant_site_url( - engagement.tenant_id, unsubscribe_path) email_environment = get_gc_notify_config('EMAIL_ENVIRONMENT') - tenant_name = EmailVerificationService._get_tenant_name( - engagement.tenant_id) args = { - 'engagement_name': engagement_name, + 'project_name': project_name, 'confirm_url': confirm_url, - 'unsubscribe_url': unsubscribe_url, 'email_environment': email_environment, 'tenant_name': tenant_name, + 'is_subscribing_to_tenant': is_subscribing_to_tenant, + 'is_subscribing_to_project': is_subscribing_to_project, } - subject = subject_template.format(engagement_name=engagement_name) + subject = get_gc_notify_config('SUBSCRIBE_EMAIL_SUBJECT') body = template.render( - engagement_name=args.get('engagement_name'), + project_name=args.get('project_name'), confirm_url=args.get('confirm_url'), - unsubscribe_url=args.get('unsubscribe_url'), email_environment=args.get('email_environment'), tenant_name=args.get('tenant_name'), + is_subscribing_to_tenant=args.get('is_subscribing_to_tenant'), + is_subscribing_to_project=args.get('is_subscribing_to_project'), ) return subject, body, args, template_id @@ -216,6 +225,22 @@ def _get_tenant_name(tenant_id): tenant = TenantModel.find_by_id(tenant_id) return tenant.name + @staticmethod + def _get_project_name(subscription_type, tenant_name, engagement): + metadata_model: EngagementMetadataModel = EngagementMetadataModel.find_by_id(engagement.id) + if subscription_type == SubscriptionTypes.TENANT.value: + return tenant_name + + if subscription_type == SubscriptionTypes.PROJECT.value: + metadata_model: EngagementMetadataModel = EngagementMetadataModel.find_by_id(engagement.id) + project_name = metadata_model.project_metadata.get('project_name', None) + return project_name or engagement.name + + if subscription_type == SubscriptionTypes.ENGAGEMENT.value: + return engagement.name + + return None + @staticmethod def validate_email_verification(email_verification: EmailVerificationSchema): """Validate an email verification.""" diff --git a/met-api/templates/engagement_closing_soon.html b/met-api/templates/engagement_closing_soon.html new file mode 100644 index 000000000..a811a2ef5 --- /dev/null +++ b/met-api/templates/engagement_closing_soon.html @@ -0,0 +1,15 @@ +

The public comment period for {{ project_name }} will close on {{ end_date }} at midnight, Pacific time.

+
+

If you would like to provide your feedback, please do so before the date noted above.

+
+

If you have already provided feedback, thank you! You may ignore this email.

+
+

Provide Your Feedback

+
+

Thank you,

+
+

The {{ tenant_name }} Team

+
+

If you would no longer like to receive updates from us, you may unsubscribe.

+
+

{{email_environment}}

diff --git a/met-api/templates/publish_engagement.html b/met-api/templates/publish_engagement.html index a7afaf55c..618cdd296 100644 --- a/met-api/templates/publish_engagement.html +++ b/met-api/templates/publish_engagement.html @@ -1,12 +1,15 @@ -

Thank you for subscribing to our feedback tool.

+

A new engagement is now open {% if is_having_project %} for {% endif %} {% if is_not_having_project %} on {% endif %} {{ project_name }}.


-

This is to update you that a new engagement, {{ engagement_name }}, has been published.

+

To learn more and provide your feedback during this public comment period, please click the link below:


-

Please visit the

-link -

to learn more and provide your feedback.

+

Provide Your Feedback

+
+

The public comment period ends on {{ end_date }}.


Thank you,


-

The Team at the Environmental Assessment Office

+

The {{ tenant_name }} Team

+
+

If you would no longer like to receive updates from us, you may unsubscribe.

+

{{email_environment}}

diff --git a/met-api/templates/subscribe_email.html b/met-api/templates/subscribe_email.html index 4b7b3d0c6..339c10d06 100644 --- a/met-api/templates/subscribe_email.html +++ b/met-api/templates/subscribe_email.html @@ -1,9 +1,10 @@ -

Thank you for subscribing to {{ engagement_name }}.

-

Please click the link below to confirm your subscription.

+

Thank you for signing up to receive updates {% if is_subscribing_to_tenant %} on {% endif %} {% if is_subscribing_to_project %} for {% endif %} {{ project_name }}.

+
+

To receive updates, you must confirm your subscription by clicking the link below:


Confirm Subscription
-

Once you confirm your email we will send you updates about related engagements.

+

If you have received this in error you may ignore this email.


Thank you,


diff --git a/met-cron/config.py b/met-cron/config.py index 32ae7cc11..48f0db72b 100644 --- a/met-cron/config.py +++ b/met-cron/config.py @@ -132,12 +132,18 @@ class _Config(): # pylint: disable=too-few-public-methods # needed for publish emails for met api ENGAGEMENT_VIEW_PATH = os.getenv('ENGAGEMENT_VIEW_PATH', '/engagements/{engagement_id}/view') ENGAGEMENT_VIEW_PATH_SLUG = os.getenv('ENGAGEMENT_VIEW_PATH_SLUG', '/{slug}') + UNSUBSCRIBE_PATH = os.getenv('UNSUBSCRIBE_PATH', '/engagements/{engagement_id}/unsubscribe/{participant_id}') # The GC notify email variables # Publish Email Service EMAIL_SECRET_KEY = os.getenv('EMAIL_SECRET_KEY', 'secret') PUBLISH_ENGAGEMENT_EMAIL_TEMPLATE_ID = os.getenv('PUBLISH_ENGAGEMENT_EMAIL_TEMPLATE_ID') - PUBLISH_ENGAGEMENT_EMAIL_SUBJECT = os.getenv('PUBLISH_ENGAGEMENT_EMAIL_SUBJECT', 'New {engagement_name} published') + PUBLISH_ENGAGEMENT_EMAIL_SUBJECT = os.getenv('PUBLISH_ENGAGEMENT_EMAIL_SUBJECT', 'Share your feedback') + + # Closing Soon Email Service + ENGAGEMENT_CLOSING_SOON_EMAIL_TEMPLATE_ID = os.getenv('ENGAGEMENT_CLOSING_SOON_EMAIL_TEMPLATE_ID') + ENGAGEMENT_CLOSING_SOON_EMAIL_SUBJECT = os.getenv('ENGAGEMENT_CLOSING_SOON_EMAIL_SUBJECT', + 'Public comment period closes in 2 days') # Email Service ENGAGEMENT_CLOSEOUT_EMAIL_TEMPLATE_ID = os.getenv('ENGAGEMENT_CLOSEOUT_EMAIL_TEMPLATE_ID') diff --git a/met-cron/cron/crontab b/met-cron/cron/crontab index 15fa6fdb4..deb6609bc 100644 --- a/met-cron/cron/crontab +++ b/met-cron/cron/crontab @@ -1,3 +1,5 @@ +# ENGAGEMENT CLOSING SOON Runs At 16:00 on every day-of-week from Monday through Friday (UTC). +0 16 * * 1-5 default cd /met-cron && ./run_engagement_closing_soon.sh # ANALYTICS ETL Runs At every hour (UTC). 0 * * * * default cd /met-cron && ./run_met_etl.sh # ENGAGEMENT CLOSEOUT Runs At 17:00 on every day-of-week from Monday through Friday (UTC). diff --git a/met-cron/invoke_jobs.py b/met-cron/invoke_jobs.py index fce60f599..9713848a2 100644 --- a/met-cron/invoke_jobs.py +++ b/met-cron/invoke_jobs.py @@ -58,6 +58,7 @@ def shell_context(): def run(job_name): + from tasks.closing_soon_mailer import EngagementClosingSoonMailer from tasks.met_closeout import MetEngagementCloseout from tasks.met_publish import MetEngagementPublish from tasks.met_purge import MetPurge @@ -83,6 +84,9 @@ def run(job_name): elif job_name == 'PUBLISH_EMAIL': SubscriptionMailer.do_email() application.logger.info('<<<< Completed MET PUBLISH_EMAIL >>>>') + elif job_name == 'CLOSING_SOON_EMAIL': + EngagementClosingSoonMailer.do_email() + application.logger.info('<<<< Completed MET CLOSING_SOON_EMAIL >>>>') else: application.logger.debug('No valid args passed.Exiting job without running any ***************') diff --git a/met-cron/run_engagement_closing_soon.sh b/met-cron/run_engagement_closing_soon.sh new file mode 100644 index 000000000..0193e7521 --- /dev/null +++ b/met-cron/run_engagement_closing_soon.sh @@ -0,0 +1,3 @@ +#! /bin/sh +echo 'run invoke_jobs.py CLOSING_SOON_EMAIL' +python3 invoke_jobs.py CLOSING_SOON_EMAIL \ No newline at end of file diff --git a/met-cron/src/met_cron/services/closing_soon_mail_service.py b/met-cron/src/met_cron/services/closing_soon_mail_service.py new file mode 100644 index 000000000..314e5e317 --- /dev/null +++ b/met-cron/src/met_cron/services/closing_soon_mail_service.py @@ -0,0 +1,26 @@ +from flask import current_app +from met_api.models.engagement import Engagement as EngagementModel +from met_api.utils.template import Template +from met_cron.services.mail_service import EmailService + + +class ClosingSoonEmailService: # pylint: disable=too-few-public-methods + """Mail for newly published engagements""" + + @staticmethod + def do_mail(): + """Send mail by listening to the email_queue. + + 1. Get N number of unprocessed recoreds from the email_queue table + 2. Process each mail and send it to subscribed users + + """ + engagements_closing_soon = EngagementModel.get_engagements_closing_soon() + template_id = current_app.config.get('ENGAGEMENT_CLOSING_SOON_EMAIL_TEMPLATE_ID', None) + subject = current_app.config.get('ENGAGEMENT_CLOSING_SOON_EMAIL_SUBJECT') + template = Template.get_template('engagement_closing_soon.html') + for engagement in engagements_closing_soon: + # Process each mails.First set status as PROCESSING + + EmailService._send_email_notification_for_subscription(engagement.id, template_id, + subject, template) diff --git a/met-cron/src/met_cron/services/mail_service.py b/met-cron/src/met_cron/services/mail_service.py index 2db4e9900..981300ea4 100644 --- a/met-cron/src/met_cron/services/mail_service.py +++ b/met-cron/src/met_cron/services/mail_service.py @@ -1,17 +1,15 @@ -from datetime import datetime from http import HTTPStatus from typing import List from flask import current_app -from met_api.constants.notification_status import NotificationStatus from met_api.exceptions.business_exception import BusinessException -from met_api.models.email_queue import EmailQueue as EmailQueueModel +from met_api.models import Tenant as TenantModel from met_api.models.engagement import Engagement as EngagementModel +from met_api.models.engagement_metadata import EngagementMetadataModel from met_api.models.participant import Participant as ParticipantModel from met_api.models.subscription import Subscription as SubscriptionModel from met_api.utils import notification -from met_api.utils.enums import SourceType, SourceAction -from met_api.utils.template import Template +from met_cron.utils.subscription_checker import CheckSubscription from met_cron.models.db import db @@ -20,91 +18,85 @@ class EmailService: # pylint: disable=too-few-public-methods """Mail on updates.""" @staticmethod - def do_mail(): - """Send mail by listening to the email_queue. + def _send_email_notification_for_subscription(engagement_id, template_id, subject, template): + engagement: EngagementModel = EngagementModel.find_by_id(engagement_id) - 1. Get N number of unprocessed recoreds from the email_queue table - 2. Process each mail and send it to subscribed users - - """ - email_batch_size: int = int(current_app.config.get('MAIL_BATCH_SIZE')) - mails = EmailQueueModel.get_unprocessed_mails(email_batch_size) - mail: EmailQueueModel - for mail in mails: - # Process each mails.First set status as PROCESSING - mail.notification_status = NotificationStatus.PROCESSING.value - mail.updated_date = datetime.utcnow() - mail.commit() - # Commenting it out for now since we are not sending email for engagement creation - #if mail.entity_type == SourceType.ENGAGEMENT.value and mail.action == SourceAction.CREATED.value: - # EmailService._send_mail_for_new_engagement(mail) - # mail.notification_status = NotificationStatus.SENT.value - # mail.updated_date = datetime.utcnow() - # mail.commit() - if mail.entity_type == SourceType.ENGAGEMENT.value and mail.action == SourceAction.PUBLISHED.value: - EmailService._send_mail_for_published_engagement(mail) - mail.notification_status = NotificationStatus.SENT.value - mail.updated_date = datetime.utcnow() - mail.commit() - - @staticmethod - def _send_mail_for_published_engagement(mail): - eng: EngagementModel = EngagementModel.find_by_id(mail.entity_id) - template_id = current_app.config.get('PUBLISH_ENGAGEMENT_EMAIL_TEMPLATE_ID', None) - subject, body, args = EmailService._render_email_template(eng) # find emails from the subscription table subscription_list: List[SubscriptionModel] = db.session.query(SubscriptionModel).distinct().filter( SubscriptionModel.is_subscribed == True).all() subscriber: SubscriptionModel email_list = [] for subscriber in subscription_list: - try: - Participant = ParticipantModel.find_by_id(subscriber.participant_id) - if Participant.email_address is not None: - email_address = ParticipantModel.decode_email(Participant.email_address) - if email_address not in email_list: - email_list.append(email_address) - except Exception as exc: # noqa: B902 - current_app.logger.error(' { const dispatch = useAppDispatch(); @@ -37,19 +39,23 @@ const SubscribeWidget = ({ widget }: { widget: Widget }) => { const [email, setEmail] = useState(''); const [open, setOpen] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [subscriptionType, setSubscriptionType] = useState(''); + const [subscriptionType, setSubscriptionType] = useState(defaultType); const subscribeWidget = widgets.find((widget) => widget.widget_type_id === WidgetType.Subscribe); const [subscribeItems, setSubscribeItems] = useState([]); const [isLoadingSubscribeItems, setIsLoadingSubscribeItems] = useState(true); + const tenant: TenantState = useAppSelector((state) => state.tenant); const sendEmail = async () => { try { setIsSaving(true); - const email_verification = await createEmailVerification({ - email_address: email, - survey_id: savedEngagement.surveys[0].id, - type: EmailVerificationType.Subscribe, - }); + const email_verification = await createSubscribeEmailVerification( + { + email_address: email, + survey_id: savedEngagement.surveys[0].id, + type: EmailVerificationType.Subscribe, + }, + subscriptionType ? subscriptionType : defaultType, + ); await createSubscription({ engagement_id: savedEngagement.id, @@ -216,8 +222,7 @@ const SubscribeWidget = ({ widget }: { widget: Widget }) => { control={} label={ - I want to receive updates for all the projects at the Environmental Assessment - Office + I want to receive updates for all the projects at the {tenant.name} } /> diff --git a/met-web/src/services/emailVerificationService/index.ts b/met-web/src/services/emailVerificationService/index.ts index ee7b43e6c..6760523a7 100644 --- a/met-web/src/services/emailVerificationService/index.ts +++ b/met-web/src/services/emailVerificationService/index.ts @@ -40,3 +40,20 @@ export const createEmailVerification = async (request: CreateEmailVerification): return Promise.reject(err); } }; + +export const createSubscribeEmailVerification = async ( + request: CreateEmailVerification, + subscription_type: string, +): Promise => { + try { + const url = replaceUrl( + Endpoints.EmailVerification.CREATE_SUBSCRIBE, + 'subscription_type', + String(subscription_type), + ); + const response = await http.PostRequest(url, request); + return response.data; + } catch (err) { + return Promise.reject(err); + } +};