"""Logic Tier for Podcast.""" from podcast import config from podcast.connectors import mysql from podcast.connectors import sentry from podcast.constants import asset_types as asset_types_consts from podcast.constants import common from podcast.constants import error from podcast.constants.feature_flag import FEATURE_PODCAST_IA_RESTRUCTURE from podcast.logic import megaphone from podcast.logic import podcast_links from podcast.logic import user as user_logic from podcast.logic import user_v2 as user_v2_logic from podcast.models import episode as episode_model from podcast.models import ows_asset_transcoder as oat from podcast.models import participant as participant_model from podcast.models import podcast as podcast_model from podcast.models import podcast_season as season_model from podcast.models import s3 from podcast.models import show_family as show_family_model from podcast.models import user as user_model from podcast.utils import api_utils from podcast.utils import exc from podcast.utils import feature_flag_utils from podcast.utils import signed_urls from podcast.utils.exc import OwsError def create_podcast(data): """Create podcast function. Returns: dict: containing a dict with the created podcast. """ current_user = user_logic.get_current_user() user_logic.current_user_has_read_only_access_then_raise(role=current_user['role']) if 'network_id' in data: network_ids = user_logic.network_ids_for_current_user(current_user=current_user) if data['network_id'] not in network_ids: raise exc.OwsError.forbidden() with mysql.pod_db_session() as session: artwork_filename = None if 'artwork_filename' in data: artwork_filename = data.pop('artwork_filename') participants = data.pop('participants', None) seasons = data.pop('seasons', None) created_show_family = show_family_model.create_show_family( { 'network_id': data['network_id'], 'title': data['title'], 'created_by': current_user['id'], 'updated_by': current_user['id'] }, session) data['show_family_id'] = created_show_family['id'] created_podcast = podcast_model.create_podcast(data, session) podcast_id = created_podcast['id'] if artwork_filename: # test podcasts may not have artwork oat.commit(artwork_filename, podcast_id) if participants: created_participants = participant_model.create_participants(podcast_id, participants, session) created_podcast['participants'] = created_participants if seasons: if data.get('show_type') == common.SHOW_TYPE_SERIAL: season_model.create_seasons(podcast_id, seasons, False, session) else: raise exc.OwsError.bad_request(error.ERROR_SEASONS) if data.get('feed_type') in (common.APPLE_SUBSCRIPTION, common.YOUTUBE): return created_podcast else: podcast_art = validate_artwork_asset(podcast_id) if artwork_filename else {} megaphone_response = megaphone.create_or_update_podcast(None, created_podcast, podcast_art) podcast_links.create_new_podcast_links(podcast_id, megaphone_response['uid']) return podcast_model.update_podcast(podcast_id, {'megaphone_id': megaphone_response['id']}, session) def validate_artwork_asset(podcast_id): """Check podcast assets to be valid.""" podcast_art = oat.get_podcast_assets(podcast_id) if not podcast_art: raise exc.OwsError(error.ERROR_ASSET_FINAL_NOT_FOUND) s3.check_s3_file_exists( bucket_name=config.OUTPUT_ASSETS_BUCKET_NAME, file_key=podcast_art[asset_types_consts.SUBTYPE_XLARGE_COVER] ) return podcast_art def update_podcast(podcast_id, data): """Update podcast function. Args: podcast_id (int): The unique identifier of the podcast data (dict): The payload with podcast metadata Returns: dict: containing a dict with the updated podcast. """ user_logic.current_user_has_read_only_access_then_raise() podcast = podcast_model.get_podcast_by_id(podcast_id) is_podcast_ia_restructure = feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE) show_family_id = podcast['show_family_id'] if is_podcast_ia_restructure: user_v2_logic.current_user_owns_show_family_or_raise(show_family_id) else: user_logic.current_user_owns_podcast_or_raise(podcast) if 'artwork_filename' in data: oat.commit_or_delete_asset(data.pop('artwork_filename'), podcast_id, 'artwork', 'podcast') with mysql.pod_db_session() as session: if 'participants' in data: participant_model.create_participants(podcast_id, data.pop('participants'), session) seasons = data.pop('seasons', None) if seasons: if podcast['show_type'] == common.SHOW_TYPE_SERIAL: season_model.create_seasons(podcast_id, seasons, True, session) else: raise exc.OwsError.bad_request(error.ERROR_SEASONS) podcast_payload = podcast_model.update_podcast(podcast_id, data, session) if podcast['feed_type'] not in (common.APPLE_SUBSCRIPTION, common.YOUTUBE): podcast_art = validate_artwork_asset(podcast_id) megaphone_id = podcast_payload['megaphone_id'] megaphone.create_or_update_podcast(megaphone_id, podcast_payload, podcast_art) if is_podcast_ia_restructure: try: earliest_feed = podcast_model.get_earliest_feeds_by_show_family_ids([show_family_id])['items'][0] if earliest_feed['id'] == podcast_payload['id']: show_family = show_family_model.get_show_family_by_id(show_family_id) if show_family['title'] != podcast_payload['title']: show_family_model.update_show_family( show_family_id, {'title': podcast_payload['title']}, session) except Exception as err: if sentry.sentry_client: sentry.send_to_sentry(err, 500, {}, error.ERROR_MESSAGE_INTERNAL_SERVER) return podcast_payload def delete_podcast(podcast_id): """Delete podcast function. Makes soft delete on our microservice's side. Makes hard delete on megaphone's side Args: podcast_id (int): The unique identifier of the podcast Returns: dict: containing a dict with the deleted podcast. """ if not podcast_id: raise exc.OwsError.bad_request(error.ERROR_MESSAGE_BAD_PARAMS) user_logic.current_user_is_org_admin_or_raise() with mysql.pod_db_session() as session: podcast = podcast_model.delete_podcast(podcast_id, session) megaphone_id = podcast['megaphone_id'] network_id = podcast['network_id'] if megaphone_id: megaphone.delete_podcast(megaphone_id, network_id) user_model.delete_favorite_podcast_by_podcast_id(podcast_id, session) show_family_id = podcast['show_family_id'] active_podcasts = podcast_model.get_feeds_by_show_family_id(show_family_id, session)['items'] if not active_podcasts: show_family_model.delete_show_family(show_family_id, session) return podcast def get_podcasts(limit=0, offset=0, network_ids=[], ids=[]): """Get all podcasts. Filters podcasts in a broader perspective i.e returns podcasts in podcast ids or network ids. Get all owned podcast ids then filter those ids based on ids or network ids filters. If network ids then filter out only the accesible podcast ids. If ids or network ids are unaccessible then raise error. Args: limit (int): how many podcasts to retrieve. offset (int): the offset (for pagination). network_ids (list of int): network ids to fetch podcasts. ids (list of int): podcast ids to fetch podcasts. Returns: dict: containing a list of dicts with podcast metadata. """ current_user = user_logic.get_current_user() all_owned_podcast_ids = user_logic.podcast_ids_owned_by_current_user(current_user) if not ids and not network_ids: podcast_ids = all_owned_podcast_ids else: podcast_ids = [] if ids: if not set(ids).issubset(all_owned_podcast_ids): raise exc.OwsError.forbidden() podcast_ids += ids if network_ids: user_logic.current_user_owns_network_ids_or_raise(network_ids, True, current_user) networks_podcast_ids = podcast_model.get_podcast_ids_by_network_ids(network_ids) podcast_ids += list(set(all_owned_podcast_ids).intersection(set(networks_podcast_ids))) podcasts = podcast_model.get_podcasts(limit, offset, podcast_ids) attach_num_episodes(podcasts['items']) return podcasts def get_podcast_by_id(podcast_id): """Get a podcast by id. Args: podcast_id (int): The podcast unique identifier. Returns: dict: containing a list of dicts with podcast metadata. """ podcast = podcast_model.get_podcast_by_id(podcast_id) if feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE): user_v2_logic.current_user_owns_show_family_or_raise(podcast['show_family_id']) else: user_logic.current_user_owns_podcast_or_raise(podcast) return podcast def get_podcasts_by_ids(ids): """Get podcasts by ids. Args: ids (list): The podcast unique identifiers. Returns: dict: containing a list of dicts with podcasts metadata. """ podcasts = podcast_model.get_podcasts_by_ids(ids) if feature_flag_utils.get_feature_flag(FEATURE_PODCAST_IA_RESTRUCTURE): user_v2_logic.current_user_owns_show_families_or_raise(podcasts['items']) else: user_logic.current_user_owns_podcasts_or_raise(podcasts['items']) attach_num_episodes(podcasts['items']) return podcasts def attach_num_episodes(podcasts): """Attach number of episodes to each podcast object.""" podcast_ids = [podcast['id'] for podcast in podcasts] podcasts_num_episodes = episode_model.get_num_episodes_for_podcast_ids(podcast_ids) for podcast in podcasts: podcast['num_episodes'] = podcasts_num_episodes.get(podcast['id'], 0) def get_public_and_private_rss_podcasts(): """Get podcasts having feed-type as 'public-rss' or 'private-rss' based on user access. Also, attach num_episodes for each podcast. Returns: dict: containing a list of dicts with podcast metadata. """ podcasts = user_v2_logic.public_and_private_rss_podcasts_owned_by_current_user() attach_num_episodes(podcasts['items']) return podcasts def get_podcast_original_artwork_asset(podcast_id): """Get podcast's signed artwork url from output bucket valid for 10 mins. Checks user access to the podcast. Get podcast artwork asset upload whose status is enoding_completed from oat. Format artwork_asset with filename and asset type from OAT response, will be used as key in s3 buckets. Check if the artwork file already exists in the output bucket. If not, copy artwork file from the input to output bucket. Generate signed url from output_bucket_cdn and artwork_filename using cloudfront_signer having validity for 10 mins. Args: podcast_id (int): The podcast unique identifier. Returns: dict: contains original artwork url. """ user_logic.current_user_owns_podcast_id_or_raise(podcast_id) artwork_asset = oat.get_object_asset_by_id_and_type( podcast_id, 'podcast', [asset_types_consts.TYPE_FILE_TIF, asset_types_consts.TYPE_FILE_JPG]) artwork_filename = '{}.{}'.format(artwork_asset['filename'], artwork_asset['asset_type'].lower()) try: s3.check_s3_file_exists(config.OUTPUT_ASSETS_BUCKET_NAME, artwork_filename) except OwsError: s3.copy_s3_file( config.INPUT_ASSETS_BUCKET_NAME, artwork_filename, config.OUTPUT_ASSETS_BUCKET_NAME, artwork_filename ) original_artwork_url = signed_urls.sign_url(api_utils.asset_url(artwork_filename)) return {'original_artwork_url': original_artwork_url}