"""Application Handlers. Requests are redirected to handlers, which are responsible for getting information from the URL and passing it down to the logic layer. The way each layer talks to each other is through Response objects which defines the type status of the data and the data itself. Please note: the Orchard uses the term handlers over views as convention for clarity See: oto.response for more details. """ import datetime import re from dateutil.relativedelta import relativedelta from flask import g from flask import jsonify from flask import request from flask import send_file from owsrequest.flask_request import request_context_from_headers from owsresponse import response from owsresponse.adaptors.flask import flaskify from werkzeug.exceptions import HTTPException from podcast import config from podcast.api import app from podcast.constants import schema from podcast.constants.ad_action import ALL_TIME from podcast.logic import ad_action from podcast.logic import ad_action_comment from podcast.logic import analytics from podcast.logic import campaign from podcast.logic import category from podcast.logic import episode from podcast.logic import episode_links from podcast.logic import episode_replication_status from podcast.logic import feed from podcast.logic import generate_upload_token from podcast.logic import insertion_point from podcast.logic import jobs from podcast.logic import network from podcast.logic import podcast from podcast.logic import podcast_links from podcast.logic import podcast_season from podcast.logic import search from podcast.logic import show_family from podcast.logic import transcription from podcast.logic import user from podcast.logic import user_podcast_settings from podcast.logic import user_v2 from podcast.utils.api_utils import validate_request_data from podcast.utils.api_utils import validate_request_query from podcast.utils.datetime_json_encoder import Encoder from podcast.utils.exc import OwsError def responsify_and_flaskify_success(message): """Wrap the return value with Response and flaskify.""" return flaskify(response.Response(message=message), encoder=Encoder) @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.route('/search', methods=['GET']) def search_all(): """Search across networks, podcasts and episodes. Returns: flask.Response: Response containing success or error message. """ text = request.args.get('text', '') # escape wildcard characters formatted_text = re.sub(r'([_/%])+', r'/\1', text) return responsify_and_flaskify_success(search.search(formatted_text)) @app.route('/podcasts', methods=['POST']) @validate_request_data(schema.CreatePodcastSchema()) def create_podcast(data): """Create a podcast. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.create_podcast(data)) @app.route('/feed', methods=['POST']) @validate_request_data(schema.CreateFeedSchema()) def create_feed(data): """Create a feed. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(feed.create_feed(data)) @app.route('/podcasts/', methods=['PUT']) @validate_request_data(schema.UpdatePodcastSchema()) def update_podcast(podcast_id, data): """Update a podcast. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.update_podcast(podcast_id, data)) @app.route('/podcasts/', methods=['DELETE']) def delete_podcast(podcast_id): """Delete a podcast. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.delete_podcast(podcast_id)) @app.route('/podcasts', methods=['GET']) def get_podcasts(): """Get all podcasts. Returns: flask.Response: Response containing success or error message. """ limit = int(request.args.get('limit', 0)) offset = int(request.args.get('offset', 0)) network_ids = list(map(int, request.args.getlist('network_ids'))) ids = list(map(int, request.args.getlist('ids'))) return responsify_and_flaskify_success(podcast.get_podcasts(limit, offset, network_ids, ids)) @app.route('/episodes/most-recent', methods=['GET']) def get_most_recent_episodes(): """Get most recent episodes. Returns: flask.Response: Response containing success or error message. """ limit = int(request.args.get('limit', 5)) return responsify_and_flaskify_success(episode.get_most_recent(limit)) @app.route('/podcasts-by-id', methods=['GET']) def get_podcasts_by_ids(): """Get podcasts by ids. Returns: flask.Response: Response containing success or error message. """ ids = _get_ids(request) return responsify_and_flaskify_success(podcast.get_podcasts_by_ids(ids)) @app.route('/feeds-by-show-family-id/', methods=['GET']) def get_feeds_by_show_family_id(show_family_id): """Get feeds(podcasts) by show family id. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(feed.get_feeds_by_show_family_id(show_family_id)) @app.route('/podcasts/seasons-status', methods=['POST']) def get_seasons_status(): """Get seasons status. Returns: flask.Response: Response containing success or error message. """ ids = request.get_json()['ids'] return responsify_and_flaskify_success(podcast_season.get_seasons_status(ids)) @app.route('/episodes-by-id', methods=['GET']) def get_episodes_by_ids(): """Get episodes by ids. Returns: flask.Response: Response containing success or error message. """ ids = _get_ids(request) return responsify_and_flaskify_success(episode.get_episodes_by_ids(ids)) @app.route('/podcasts//episodes', methods=['POST']) def create_episode(podcast_id): """Create an episode. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ data = request.get_json() return responsify_and_flaskify_success( episode.create_episode(podcast_id, schema.validate_episode(data))) @app.route('/podcasts//episodes/planned-inventory', methods=['POST']) @validate_request_data(schema.PlannedInventorySchema()) def create_planned_inventory(podcast_id, data): """Create planned inventory. Args: podcast_id (int): The unique identifier of the podcast data (dict): The dates and expected ads count Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.create_planned_inventory(podcast_id, data)) @app.route('/podcasts//episodes/', methods=['PUT']) def update_episode(podcast_id, episode_id): """Update an episode. Args: podcast_id (int): The unique identifier of the podcast episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ data = request.get_json() return responsify_and_flaskify_success( episode.update_episode(podcast_id, episode_id, schema.validate_episode(data))) @app.route('/episode//reupload-mp3', methods=['PUT']) def reupload_mp3_to_megaphone(episode_id): """Reupload mp3 to megaphone. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.reupload_mp3_to_megaphone(episode_id)) @app.route('/episode//transcript', methods=['PUT']) def update_episode_transcript(episode_id): """Update an episode transcript. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ data = request.get_json() return responsify_and_flaskify_success( transcription.update_transcription(episode_id, data)) @app.route('/episode//transcript/file', methods=['GET']) def get_episode_transcript_file(episode_id): """Get an episode transcript in word doc. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ file_type = request.args.get('file_type', 'doc') interval = int(request.args.get('interval', 10)) transcription_file = transcription.get_file(episode_id, file_type, interval) file_name = f'report.{file_type}' return send_file(transcription_file, as_attachment=True, download_name=file_name) @app.route('/episode//transcript', methods=['GET']) def get_episode_transcription(episode_id): """Get an episode transcription. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(transcription.get_transcription(episode_id)) @app.route('/episode//transcript', methods=['DELETE']) def delete_episode_transcription(episode_id): """Delete an episode transcription. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(transcription.delete_by_episode_id(episode_id)) @app.route('/podcasts//episodes', methods=['GET']) def get_episodes(podcast_id): """Retrieve all the episodes for a podcast. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ limit = int(request.args.get('limit', 0)) offset = int(request.args.get('offset', 0)) filter_by_state = request.args.get('filter_by_state', 'ALL') order_by = request.args.get('order_by', 'published_date') sort_order = request.args.get('sort_order', 'asc') start_date = _null_to_none(request.args.get('start_date', None)) end_date = _null_to_none(request.args.get('end_date', None)) return responsify_and_flaskify_success( episode.get_episodes( podcast_id, limit, offset, filter_by_state, order_by, sort_order, start_date, end_date )) @app.route('/podcasts//episodes/', methods=['GET']) def get_episode_by_id(podcast_id, episode_id): """Get a episode by id. Args: podcast_id (int): The unique identifier of the podcast episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.get_episode_by_id(podcast_id, episode_id)) @app.route('/podcasts//episodes/', methods=['DELETE']) def delete_episode(podcast_id, episode_id): """Delete an episode. Args: podcast_id (int): The unique identifier of the podcast episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.delete_episode(podcast_id, episode_id)) @app.route('/categories', methods=['GET']) def get_categories(): """Get all categories. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(category.get_categories()) @app.route('/podcasts//episodes//points', methods=['POST']) @validate_request_data(schema.InsertionPointsPayloadSchema(many=True)) def create_insertion_points(podcast_id, episode_id, data): """Create or update insertion points for episode. Args: podcast_id (int): The unique identifier of the podcast episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(insertion_point.create_insertion_points( podcast_id, episode_id, data)) @app.route('/networks', methods=['GET']) def get_networks(): """Get all available networks. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(network.get_networks()) @app.route('/show-family-networks', methods=['GET']) def get_show_family_networks(): """Get all available networks along with show family level networks. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(network.get_show_family_networks()) @app.route('/networks', methods=['POST']) @validate_request_data(schema.CreateNetworkSchema()) def create_network(data): """Get all available networks. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(network.create_network(data['name'], data['code'], data['is_sony'])) @app.route('/networks/', methods=['DELETE']) def delete_network(delete_id): """Delete network. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(network.delete_network(delete_id)) @app.route('/networks-by-id', methods=['GET']) def get_networks_by_ids(): """Get networks by ids. Args: ids (list of int/str): The unique identifiers of the networks Returns: flask.Response: Response containing success or error message. """ ids = _get_ids(request) id_ints = [int(string_id) for string_id in ids] return responsify_and_flaskify_success(network.get_networks_by_ids(id_ints)) @app.route('/assets/upload-token', methods=['GET']) def upload_token(): """Get upload token. Args: user_id (str): User identity extracted from headers Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(generate_upload_token.get_upload_permission()) @app.route('/ad-reads', methods=['GET']) @validate_request_query(schema.GetAdReadSchema()) def get_ad_reads(data): """Get all ad reads. Returns: flask.Response: Response containing success or error message """ is_archived = data.get('is_archived', False) only_show_mine = data.get('only_show_mine', False) limit = data.get('limit', 9999) offset = data.get('offset', 0) date_range = data.get('date_range', ALL_TIME) return responsify_and_flaskify_success(ad_action.get_ad_actions( is_archived, only_show_mine, limit, offset, date_range)) @app.route('/ad-reads', methods=['POST']) @validate_request_data(schema.AdReadSchema()) def create_ad_read(data): """Create ad read. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(ad_action.create_ad_action(data)) @app.route('/does-ad-read-exist', methods=['GET']) @validate_request_query(schema.DoesAdReadExistSchema()) def does_ad_read_exist(data): """Check if ad read exists. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(ad_action.does_ad_action_exist(data)) @app.route('/ad-reads/', methods=['PUT']) @validate_request_data(schema.AdReadSchema(), partial=True) def update_ad_read(ad_read_id, data): """Update ad read. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(ad_action.update_ad_action(ad_read_id, data)) @app.route('/ad-read-virus-free', methods=['POST']) def set_ad_read_virus_free(): """Update ad read's is_virus_free to True. Returns: flask.Response: Response containing success or error message """ filename = request.get_json()['filename'] return responsify_and_flaskify_success( ad_action.set_virus_free(filename) ) @app.route('/ad-reads/', methods=['DELETE']) def delete_ad_read(ad_action_id): """Delete ad action. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(ad_action.delete_ad_action(ad_action_id)) @app.route('/ad-read-comments-by-id/', methods=['GET']) @validate_request_query(schema.GetAdReadCommentsSchema()) def get_ad_read_comments(ad_action_id, data): """Get all ad read comments by ad read id. Returns: flask.Response: Response containing success or error message """ limit = data.get('limit', 50) offset = data.get('offset', 0) return responsify_and_flaskify_success(ad_action_comment.get_ad_action_comments( ad_action_id, limit, offset)) @app.route('/ad-read-comments-count', methods=['GET']) def get_ad_read_comments_count(): """Get ad read comments count by ad read ids. Returns: flask.Response: Response containing success or error message """ ad_action_ids = _get_ids(request, 'ad_read_ids') return responsify_and_flaskify_success(ad_action_comment.get_ad_action_comments_count(ad_action_ids)) @app.route('/ad-read-comment', methods=['POST']) @validate_request_data(schema.CreateAdReadCommentSchema()) def create_add_read_comment(data): """Add a comment to an ad action. Returns: flask.Response: Response containing success payload or error message """ return flaskify(response.Response( message=ad_action_comment.create_ad_action_comment(data), status=201), encoder=Encoder) @app.route('/ad-read-comment/', methods=['DELETE']) def delete_ad_read_comment(ad_action_comment_id): """Delete ad action comment. Args: ad_action_comment_id (int): The unique identifier of the comment to delete Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success( ad_action_comment.delete_ad_action_comment(ad_action_comment_id)) @app.route('/users', methods=['POST']) @validate_request_data(schema.CreateUserSchema()) def create_user(data): """Create a user. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.create_user(data)) @app.route('/users', methods=['GET']) @validate_request_query(schema.GetUsersSchema()) def get_users(data): """Get users. Returns: flask.Response: Response containing success or error message """ limit = data.get('limit', 100) offset = data.get('offset', 0) organization = data.get('organization') network_ids = data.get('network_ids') return responsify_and_flaskify_success(user.get_users(limit, offset, organization, network_ids)) @app.route('/users-by-id', methods=['GET']) def get_users_by_ids(): """Get users by ids. Returns: flask.Response: Response containing success or error message """ ids = _get_ids(request) return responsify_and_flaskify_success(user.get_users_by_ids(ids)) @app.route('/users/current', methods=['GET']) def get_current_user(): """Get current user. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.get_current_user()) @app.route('/users/', methods=['DELETE']) def delete_user(user_id): """Get users. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.delete_user(user_id)) @app.route('/users/', methods=['PUT']) @validate_request_data(schema.UpdateUserSchema(), partial=True) def update_user(user_id, data): """Update user. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.update_user(user_id, data)) @app.route('/users-last-login/', methods=['PUT']) @validate_request_data(schema.UpdateUserLastLoginSchema()) def update_user_last_login(user_uuid, data): """Update user last login. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.update_user_last_login(user_uuid, data)) @app.route('/users-for-networks', methods=['GET']) def users_for_networks(): """Update user. Returns: flask.Response: Response containing success or error message """ ids = [int(x) for x in _get_ids(request)] return responsify_and_flaskify_success(user.users_for_network_ids(ids)) @app.route('/users/favorites', methods=['GET']) def get_user_favorites(): """Get current users favorites. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.get_user_favorites()) @app.route('/users/settings', methods=['GET']) def get_current_user_settings(): """Get current users settings. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user_podcast_settings.get_user_settings()) @app.route('/campaigns', methods=['GET']) def get_campaigns(): """Get campaigns.""" return responsify_and_flaskify_success(campaign.campaigns()) @app.route('/campaigns//orders', methods=['GET']) def get_campaign_orders(campaign_id): """Get campaign orders.""" return responsify_and_flaskify_success(campaign.campaign_orders(campaign_id)) @app.route('/campaigns-by-id', methods=['GET']) def get_campaigns_by_megaphone_ids(): """Get campaigns.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_campaigns_by_megaphone_ids(ids)) @app.route('/orders-by-id', methods=['GET']) def get_orders_by_ids(): """Get orders.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_campaign_orders_by_megaphone_ids(ids)) @app.route('/orders-by-episode-ids', methods=['GET']) def get_orders_by_episode_ids(): """Get orders by episode ids.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_campaign_orders_by_episode_ids(ids)) @app.route('/advertisements-by-order-ids', methods=['GET']) def get_advertisements_by_order_ids(): """Get advertisements by order ids.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_advertisements_by_order_ids(ids)) @app.route('/ad-actions-by-advertisement-ids', methods=['GET']) def get_ad_actions_by_advertisement_ids(): """Get ad actions by advertisement ids.""" ids = _get_ids(request) return responsify_and_flaskify_success(ad_action.get_ad_actions_by_advertisement_ids(ids)) @app.route('/podcast-upcoming-sold-percent-by-ids', methods=['GET']) def get_podcast_upcoming_sold_percent_by_ids(): """Get upcoming sold percentages by podcast ids.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_podcast_upcoming_sold_percent_by_ids(ids)) @app.route('/stores', methods=['GET']) def get_stores(): """Get stores.""" return responsify_and_flaskify_success(podcast_links.get_stores()) @app.route('/podcast-links/podcast/', methods=['GET']) def get_podcast_links(podcast_id): """Get podcast links.""" return responsify_and_flaskify_success(podcast_links.get_podcast_links(podcast_id)) @app.route('/podcast-links', methods=['POST']) @validate_request_data(schema.CreatePodcastLinksSchema()) def create_podcast_links(data): """Create podcast links.""" return responsify_and_flaskify_success(podcast_links.create_podcast_links(data)) @app.route('/episode-link', methods=['POST']) @validate_request_data(schema.CreateEpisodeLinkSchema()) def create_episode_link(data): """Create an episode link. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(episode_links.create_episode_link(data)) @app.route('/episode-link/episode//store/', methods=['DELETE']) def delete_episode_link(episode_id, store_id): """Delete an episode link. Args: episode_id (int): The unique identifier of the episode. store_id (int): The unique identifier of the store. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode_links.delete_episode_link(episode_id, store_id)) @app.route('/episode-links/podcast//episode/', methods=['GET']) def get_episode_links(podcast_id, episode_id): """Get all episode links for an episode. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(episode_links.get_episode_links(podcast_id, episode_id)) @app.route('/campaigns//orders//advertisements', methods=['GET']) def get_advertisements(campaign_id, order_id): """Get advertisements.""" return responsify_and_flaskify_success(campaign.advertisements(campaign_id, order_id)) @app.route('/advertisements-by-ids', methods=['GET']) def get_advertisements_by_ids(): """Get advertisements by megaphone ids.""" ids = _get_ids(request) return responsify_and_flaskify_success(campaign.get_advertisements_by_megaphone_ids(ids)) @app.route('/users/settings', methods=['PUT']) @validate_request_data(schema.UserSettingsSchema()) def update_current_user_settings(data): """Update current users settings. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user_podcast_settings.update_user_settings(data)) @app.route('/users/favorites', methods=['PUT']) @validate_request_data(schema.ToggleUserFavoriteSchema()) def toggle_current_user_favorite(data): """Toggle user favorite for podcast. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.toggle_user_favorite(data)) @app.route('/users/favorites/podcast', methods=['PUT']) @validate_request_data(schema.ToggleUserFavoriteSchema()) def toggle_current_user_podcast_favorite(data): """Toggle user favorite for podcast. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.toggle_user_podcast_favorite(data)) @app.route('/users/favorites/chart', methods=['PUT']) @validate_request_data(schema.ToggleUserChartFavoriteSchema()) def toggle_current_user_chart_favorite(data): """Toggle user favorite for podcast. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user.toggle_user_chart_favorite(data)) @app.route('/check-ownership', methods=['GET']) @validate_request_query(schema.CheckOwnershipSchema()) def check_ownership(data): """Check that user can access provided object. Returns: flask.Response: Response containing success or error message """ object_type = data['object_type'] object_id = int(data['object_id']) return responsify_and_flaskify_success(user.check_ownership(object_type, object_id)) @app.route('/top-episodes', methods=['GET']) @validate_request_query(schema.TopAnalyticsSchema()) def top_episodes(data): """Check that user can access provided object. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.top_episodes( data['date_range'], data['countries'], data['players'], data['limit'], data['offset'], data['network_id'], data['podcast_id'], data['sort_field'], data['sort_order'], data.get('start_date'), data.get('end_date') )) @app.route('/episode-daily-downloads', methods=['GET']) @validate_request_query(schema.DailyEpisodeDownloadsSchema()) def episode_daily_downloads(data): """Check that user can access provided object. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.episode_daily_downloads( data['episode_ids'], data['date_range'], data['countries'], data['players'], data.get('start_date'), data.get('end_date') )) @app.route('/top-podcasts', methods=['GET']) @validate_request_query(schema.TopAnalyticsSchema()) def top_podcasts(data): """Check that user can access provided object. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.top_podcasts( data['date_range'], data['countries'], data['players'], data['limit'], data['offset'], data['network_id'], data['sort_field'], data['sort_order'], data.get('start_date'), data.get('end_date') )) @app.route('/podcast-daily-downloads', methods=['GET']) @validate_request_query(schema.DailyPodcastDownloadsSchema()) def podcast_daily_downloads(data): """Check that user can access provided object. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.podcast_daily_downloads( data['podcast_ids'], data['date_range'], data['countries'], data['players'], data.get('start_date'), data.get('end_date') )) @app.route('/top-networks', methods=['GET']) @validate_request_query(schema.TopAnalyticsSchema()) def top_networks(data): """Get top networks. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.top_networks( data['date_range'], data['countries'], data['players'], data['limit'], data['offset'], data['sort_field'], data['sort_order'], data.get('start_date'), data.get('end_date') )) @app.route('/network-daily-downloads', methods=['GET']) @validate_request_query(schema.DailyNetworkDownloadsSchema()) def network_daily_downloads(data): """Get daily stats for networks. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.network_daily_downloads( data['network_ids'], data['date_range'], data['countries'], data['players'], data.get('start_date'), data.get('end_date') )) @app.route('/country-daily-downloads', methods=['GET']) @validate_request_query(schema.DailyCountryDownloadsSchema()) def country_daily_downloads(data): """Get daily stats for countries. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.country_daily_downloads( data['object_id'], data['object_type'], data['date_range'], data['countries'], data['players'], data.get('start_date'), data.get('end_date') )) @app.route('/player-daily-downloads', methods=['GET']) @validate_request_query(schema.DailyPlayerDownloadsSchema()) def player_daily_downloads(data): """Get daily stats for players. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.player_daily_downloads( data['object_id'], data['object_type'], data['date_range'], data['countries'], data['players'], data.get('start_date'), data.get('end_date') )) @app.route('/chartable-podcasts-by-ids', methods=['GET']) def get_chartable_podcasts_by_ids(): """Get chartable podcasts by podcast ids. Returns: flask.Response: Response containing success or error message """ ids = [int(x) for x in _get_ids(request)] return responsify_and_flaskify_success(analytics.get_chartable_podcasts_by_ids( ids )) @app.route('/podcast-chart', methods=['GET']) @validate_request_query(schema.ChartableChartSchema()) def get_chartable_podcast_chart(data): """Get chartable podcast chart. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.get_chartable_podcast_chart( data['store'], data['category'], data['country'], data['report_date'] )) @app.route('/episode-chart', methods=['GET']) @validate_request_query(schema.ChartableEpisodeChartSchema()) def get_chartable_episode_chart(data): """Get chartable episode chart. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(analytics.get_chartable_episode_chart( data['category'], data['country'], data['report_date'] )) @app.route('/job/episode-gone-live', methods=['GET']) def episode_gone_live_job(): """Send email when an episode has gone live within an hour. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(jobs.episode_gone_live()) @app.route('/job/episode-going-live-tomorrow', methods=['GET']) def episode_going_live_tomorrow_job(): """Send email when an episode is going live in 24 hours. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(jobs.episode_going_live_tomorrow()) @app.route('/job/ad-past-due', methods=['GET']) def ad_past_due_job(): """Send email when an ad is past due. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(jobs.ad_past_due()) @app.route('/job/ad-due-tomorrow', methods=['GET']) def ad_due_tomorrow_job(): """Send email when an ad is due tomorrow. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(jobs.ad_due_tomorrow()) @app.route('/job/episode-spike-detection', methods=['GET']) def episode_spike_job(): """Send email when an episode spikes. Returns: flask.Response: Response containing success or error message """ yesterday = datetime.datetime.now(datetime.timezone.utc) - relativedelta(days=1) date = request.args.get('date', yesterday.strftime('%m/%d/%Y')) return responsify_and_flaskify_success(jobs.episode_spike_detection(date)) @app.route('/start-transcription/', methods=['GET']) @validate_request_query(schema.StartTranscriptionSchema()) def transcribe_episode(episode_id, data): """Transcribe episode id. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(transcription.start_for_episode(episode_id, data)) @app.route('/check-transcripts-by-episode-ids', methods=['GET']) def do_transcriptions_for_episodes_exist(): """Check whether episodes have transcripts. Returns: flask.Response: Response containing success or error message """ ids = _get_ids(request) return responsify_and_flaskify_success( transcription.do_transcriptions_for_episodes_exist(ids)) @app.route('/podcasts//episodes//is-processing', methods=['GET']) def episode_is_processing(podcast_id, episode_id): """Check if episode audio is processing in megaphone. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success( episode.get_episode_is_processing(podcast_id, episode_id)) @app.route('/users-for-order/', methods=['GET']) def users_for_order(order_id): """Get users for campaign order id. Returns: flask.Response: Response containing success or error message """ return responsify_and_flaskify_success(user_v2.users_for_order(order_id)) @app.route('/test/podcasts', methods=['POST']) @validate_request_data(schema.CreatePodcastSchema(exclude=['artwork_filename'])) def create_test_podcast(data): """Create a test podcast (without artwork). Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.create_podcast(data)) @app.route('/asset//wav', methods=['GET']) def get_episode_wav_asset(episode_id): """Get an episode's signed audio wav url from output bucket. Args: episode_id (int): The unique identifier of the episode Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.get_episode_wav_asset(episode_id)) @app.route('/replicate-single-episode', methods=['POST']) @validate_request_data(schema.ReplicateSingleEpisodeSchema()) def replicate_single_episode(data): """Replicate single episode in multiple feeds. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode.replicate_single_episode_and_assets(data)) @app.route('/episode-replication-status', methods=['PUT']) @validate_request_data(schema.UpdateEpisodeReplicationStatusSchema()) def update_episode_replication_status_by_ids(data): """Update episode replication status by status ids. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(episode_replication_status.update_episode_replication_status_by_ids(data)) @app.route('/failed-bulk-episodes-replication-status/', methods=['GET']) def get_failed_bulk_episodes_replication_status(podcast_id): """Get failed bulk episodes replication status by podcast_id. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success( episode_replication_status.get_failed_bulk_episodes_replication_status(podcast_id)) @app.route('/failed-single-episodes-replication-status/', methods=['GET']) def get_failed_single_episodes_replication_status(original_podcast_id): """Get failed episodes replication status for replication type single by original_podcast_id. Args: original_podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success( episode_replication_status.get_failed_single_episodes_replication_status(original_podcast_id)) @app.route('/show-family-by-id/', methods=['GET']) def get_show_family_by_id(show_family_id): """Get show family by id. Args: show_family_id (int): The unique identifier of the show_family. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(show_family.get_show_family_by_id(show_family_id)) @app.route('/show-families', methods=['GET']) def get_show_families(): """Get show families that user has access to. Returns: flask.Response: Response containing success or error message. """ limit = int(request.args.get('limit', 0)) offset = int(request.args.get('offset', 0)) network_ids = list(map(int, request.args.getlist('network_ids'))) ids = list(map(int, request.args.getlist('ids'))) return responsify_and_flaskify_success(show_family.get_show_families(limit, offset, network_ids, ids)) @app.route('/earliest-feeds-by-show-family-ids', methods=['GET']) def get_earliest_feeds_by_show_family_ids(): """Get earliest feeds/podcasts by show family ids. Returns: flask.Response: Response containing success or error message. """ show_family_ids = _get_ids(request, 'show_family_ids') return responsify_and_flaskify_success(feed.get_earliest_feeds_by_show_family_ids(show_family_ids)) @app.route('/replicate-episodes-and-create-assets', methods=['POST']) @validate_request_data(schema.ReplicateEpisodesSchema()) def replicate_episodes_and_create_assets(data): """Replicate episodes and related assets. Returns: flask.Response: Response containing success or error message. """ is_copy_apple_episode_id = data.get('is_copy_apple_episode_id', False) is_copy_ad_locations = data.get('is_copy_ad_locations', False) return responsify_and_flaskify_success(episode.replicate_episodes_and_create_assets( data.get('original_podcast_id'), data.get('podcast_id'), data.get('original_episode_ids'), data.get('seasons'), data.get('replication_type'), is_copy_apple_episode_id, is_copy_ad_locations)) @app.route('/public-and-private-rss-podcasts', methods=['GET']) def get_public_and_private_rss_podcasts(): """Get podcasts having feed-type as 'public-rss' or 'private-rss'. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.get_public_and_private_rss_podcasts()) @app.route('/podcast//original-artwork', methods=['GET']) def get_podcast_original_artwork_asset(podcast_id): """Get podcast's signed original artwork url from output bucket. Args: podcast_id (int): The unique identifier of the podcast Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(podcast.get_podcast_original_artwork_asset(podcast_id)) @app.errorhandler(Exception) def exception_handler(error): """Handle error when uncaught exception is raised. Default exception handler. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ if isinstance(error, OwsError): _log_to_sentry(error) return flaskify(response.Response( message={'message': error.message}, status=error.status ), encoder=Encoder) message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') _log_to_sentry(error) if isinstance(error, HTTPException): return flaskify(response.create_error_response( code=error.name, message=error.description, status=error.code )) return flaskify(response.create_fatal_response(message), encoder=Encoder) def _log_to_sentry(error): if request.headers.get('User-Agent') == 'python-requests/integration-tests': return g.log.exception(error) @app.before_request @request_context_from_headers() def before_request(): """Code to be executed before each request.""" pass def _get_ids(request, id_name='ids'): ids = request.args.get(id_name, '') return ids.split(',') if len(ids) else [] def _get_var_to_boolean(request, field): value = request.args.get(field, 'false') return value == 'true' def _null_to_none(var): if var == 'null': return None return var