"""Logic Tier for Ad Actions.""" import datetime import json import os import boto3 from botocore.exceptions import ClientError from flask import g from podcast import config from podcast.constants import ad_action as ad_action_constants from podcast.constants import user as user_constants from podcast.constants.feature_flag import FEATURE_PODCAST_IA_RESTRUCTURE from podcast.logic import email from podcast.logic import user as user_logic from podcast.logic import user_v2 as user_v2_logic from podcast.models import ad_action as ad_action_model from podcast.models import order as order_model from podcast.models import ows_asset_transcoder as oat from podcast.models.api_campaign import ApiCampaign from podcast.utils import feature_flag_utils from podcast.utils.exc import OwsError PAST_7_DAYS = 'past_7_days' PAST_28_DAYS = 'past_28_days' DUE_IN_7_DAYS = 'due_in_7_days' DUE_IN_28_DAYS = 'due_in_28_days' def _get_lambda_client(): return boto3.client('lambda', region_name='us-east-1') def _get_s3_objects(): return boto3.client('s3'), boto3.resource('s3') def _add_content_disposition(key): try: s3_client, s3_resource = _get_s3_objects() bucket = config.OUTPUT_ASSETS_BUCKET_NAME response = s3_client.head_object( Bucket=bucket, Key=key ) original_filename = response['Metadata']['original_filename'] filename, ext = os.path.splitext(original_filename) timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') content_disposition = 'attachment; filename="{}-{}{}"'.format(filename, timestamp, ext) copy_source = { 'Bucket': bucket, 'Key': key } s3_resource.meta.client.copy( copy_source, bucket, key, ExtraArgs={ 'ContentType': response['ContentType'], 'ContentDisposition': content_disposition, 'Metadata': response['Metadata'], 'MetadataDirective': 'REPLACE' }, SourceClient=s3_client ) except ClientError as error: g.log.warning(error) def _scan_for_virus(key): try: function_name = '{}-lambda-av-scan-containerized:provisioned'.format(config.ENVIRONMENT.lower()) lambda_client = _get_lambda_client() lambda_client.invoke( FunctionName=function_name, InvocationType='Event', LogType='None', Payload=json.dumps({ 'detail': { 'requestParameters': { 'bucketName': config.OUTPUT_ASSETS_BUCKET_NAME, 'key': key }, } }), ) except ClientError as error: raise OwsError.from_boto3_client_error(error, 'Error invoking anti virus lambda') def _update_megaphone_audio(ad_action): audio_url = oat.get_ad_assets(ad_action['id'])['audio_url'] megaphone_api = ApiCampaign(is_org=True) megaphone_api.update_advertisement( ad_action['campaign_id'], ad_action['order_id'], ad_action['advertisement_id'], {'backgroundAudioFileUrl': audio_url} ) def _accessible_order_network_ids(orders_network_ids, user_network_ids): network_ids = list(set(orders_network_ids) & user_network_ids) return network_ids def _get_date_filters(date_range): start_date = None end_date = None if date_range == PAST_7_DAYS: start_date = datetime.datetime.utcnow() - datetime.timedelta(weeks=1) if date_range == PAST_28_DAYS: start_date = datetime.datetime.utcnow() - datetime.timedelta(weeks=4) if date_range == DUE_IN_7_DAYS: end_date = datetime.datetime.utcnow() + datetime.timedelta(weeks=1) if date_range == DUE_IN_28_DAYS: end_date = datetime.datetime.utcnow() + datetime.timedelta(weeks=4) return start_date, end_date def create_ad_action(data): """Create ad action. Args: data (dict): The data to create Returns: dict: containing a dict with the created ad action. """ user_logic.current_user_is_admin_or_raise() # check that user has access to campaign order if user_logic.current_user_is_network_admin(): current_user_network_ids = set(user_logic.network_ids_for_current_user()) order_network_ids = order_model.get_order_by_megaphone_id(data['order_id'])['network_ids'] if len(_accessible_order_network_ids(order_network_ids, current_user_network_ids)) == 0: raise OwsError.forbidden() # skip virus scan for automation user if is_skip_virus_scan is true. if user_logic.current_user_is_automation_org_admin() and data.pop('is_skip_virus_scan', False): data['is_virus_free'] = True ad_action = ad_action_model.create_ad_action(data) else: key = data['copy_url_path'] _add_content_disposition(key) _scan_for_virus(key) ad_action = ad_action_model.create_ad_action(data) return ad_action def does_ad_action_exist(data): """Check for ad action duplicate.""" has_duplicate = ad_action_model.has_duplicate_request( data['campaign_id'], data['order_id'], data['advertisement_id'], ) if has_duplicate: return {'exists': True} return {'exists': False} def delete_ad_action(ad_action_id): """Delete ad action. Args: ad_action_id (int): The id to delete Returns: dict: containing a dict with the deleted ad action. """ current_user = user_logic.get_current_user() current_user_role = current_user['role'] user_logic.current_user_is_admin_or_raise(current_user_role) is_network_admin = current_user_role == user_constants.NETWORK_ADMIN deleted_ad_action = ad_action_model.delete_ad_action(ad_action_id, current_user, is_network_admin) if deleted_ad_action['status'] in \ (ad_action_constants.NEW, ad_action_constants.SUBMITTED, ad_action_constants.REJECTED): email.send_ad_action_assigned_to_me_deleted(deleted_ad_action) return deleted_ad_action def get_ad_actions(is_archived, only_show_mine, limit, offset, date_range): """Get ad actions. Returns: list(dict): the ad actions the user has access to. """ start_date, end_date = _get_date_filters(date_range) return ad_action_model.get_ad_actions(is_archived, only_show_mine, limit, offset, start_date, end_date) def get_ad_actions_by_advertisement_ids(ids): """Get ad actions by advertisements ids.""" return ad_action_model.get_ad_actions_by_advertisement_ids(ids) def update_ad_action(ad_read_id, data): """Update ad action. Returns: dict: the ad action. """ current_user = user_logic.get_current_user() current_user_role = current_user['role'] user_logic.current_user_has_read_only_access_then_raise(current_user_role) is_network_admin = current_user_role == user_constants.NETWORK_ADMIN is_assignees_updated = ad_action_constants.ASSIGNEE_IDS in data if ad_action_constants.ASSIGNEE_IDS in data or ad_action_constants.DUE_DATE in data or \ ad_action_constants.COPY_URL_PATH in data: user_logic.current_user_is_admin_or_raise(current_user_role) if is_network_admin and not current_user['all_networks']: ad_action = ad_action_model.get_ad_action_no_assets(ad_read_id) if feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE): user_v2_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) else: user_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) if ad_action_constants.COPY_URL_PATH in data: # Update is_virus_free as the new file might contain viruses that should be scanned data['is_virus_free'] = False data['copy_updated_date'] = datetime.datetime.now() data['is_copy_updated'] = True key = data['copy_url_path'] _add_content_disposition(key) _scan_for_virus(key) elif ad_action_constants.STATUS in data: ad_action = ad_action_model.get_ad_action_no_assets(ad_read_id) was_submitted = data['status'] == 'submitted' was_submitted_no_approval = data['status'] == 'completed' and not ad_action['requires_approval'] was_approved = data['status'] == 'completed' and ad_action['requires_approval'] was_rejected = data['status'] == 'rejected' if was_submitted or was_submitted_no_approval: if feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE): user_v2_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) else: user_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) ad_action['updated_by'] = current_user['id'] email.send_ad_action_submitted(ad_action) was_approved_or_rejected = was_rejected or was_approved if was_approved_or_rejected: user_logic.current_user_is_admin_or_raise(current_user_role) if is_network_admin: if feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE): user_v2_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) else: user_logic.current_user_owns_ad_action_or_raise(ad_action, current_user, is_network_admin) if was_submitted_no_approval or was_approved: _update_megaphone_audio(ad_action) if was_approved: email.send_ad_action_approved(ad_action) if was_rejected: ad_action['rejection_reason'] = data.get('rejection_reason', 'None given') ad_action['updated_by'] = current_user['id'] email.send_ad_action_rejected(ad_action) data['updated_by'] = current_user['id'] updated_action = ad_action_model.update_ad_action(ad_read_id, data) if is_assignees_updated: email.send_assignees_updated_on_ad_action_assigned_to_me(updated_action) if ad_action_constants.DUE_DATE in data: email.send_due_date_updated_on_ad_action_assigned_to_me(updated_action) if ad_action_constants.COPY_URL_PATH in data: email.send_script_updated_on_ad_action_assigned_to_me(updated_action) return updated_action def set_virus_free(filename): """Set an ad action as virus free. Returns: dict: The ad action. """ ad_action = ad_action_model.get_ad_action_no_assets_by_filename(filename) updated_ad_action = ad_action_model.update_ad_action( ad_action['id'], {'is_virus_free': True}) if updated_ad_action['is_copy_updated']: email.send_script_updated_on_ad_action_assigned_to_me(updated_ad_action) else: email.send_ad_action_assigned_to_me(updated_ad_action) return updated_ad_action