"""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. """ from flask import g from flask import jsonify from flask import request 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 asset_transcoder import config from asset_transcoder.api import app from asset_transcoder.constants import asset_upload as asset_upload_constants from asset_transcoder.constants import schema from asset_transcoder.constants import urls from asset_transcoder.logic import asset_final from asset_transcoder.logic import asset_status from asset_transcoder.logic import asset_upload from asset_transcoder.logic import upload_token as upload_token_logic from asset_transcoder.utils.api_utils import validate_request_data from asset_transcoder.utils.api_utils import validate_request_query from asset_transcoder.utils.datetime_json_encoder import Encoder from asset_transcoder.utils.exceptions 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('/assets-by-ids-and-types', methods=['GET']) @validate_request_query(schema.GetAssetsByIdsAndTypesSchema()) def assets_by_ids_and_types(data): """Get assets by ids and types. Args: data (dict): request query transformed into a dict after validation. Returns: flask.Response: Response containing success or error message. """ object_ids = data.get('object_ids', []) object_types = data.get('object_types', []) signed_url_policy = data.get('signed_url_policy', urls.SIGNED_URL) signed_url_duration = data.get('signed_url_duration', urls.DEFAULT_SIGNED_URL_DURATION) return responsify_and_flaskify_success(asset_upload.get_assets_by_ids_and_types( object_ids, object_types, signed_url_policy, signed_url_duration )) @app.route('/assets/', methods=['GET']) @validate_request_query(schema.GetAssetsByIdsAndTypeSchema()) def assets_by_ids_and_type(data, object_type): """Get assets by ids and type. Args: data (dict): request query transformed into a dict after validation. object_type (str): the object type Returns: flask.Response: Response containing success or error message. """ if object_type not in asset_upload_constants.OBJECT_TYPES: raise OwsError.bad_request('Invalid object type: {}'.format(object_type)) object_ids = data.get('object_ids', []) signed_url_policy = data.get('signed_url_policy', urls.SIGNED_URL) signed_url_duration = data.get('signed_url_duration', urls.DEFAULT_SIGNED_URL_DURATION) return responsify_and_flaskify_success(asset_upload.get_assets_by_ids_and_type( object_ids, object_type, signed_url_policy, signed_url_duration )) @app.route('/asset-by-id-and-type', methods=['GET']) @validate_request_query(schema.GetAssetByIdAndTypeSchema()) def asset_by_id_and_type(data): """Get asset whose encoding is completed by object_id, object_type and asset_type. Args: data (dict): request query transformed into a dict after validation. Returns: flask.Response: Response containing success or error message. """ object_id = data.get('object_id') object_type = data.get('object_type') asset_type = data.get('asset_type') return responsify_and_flaskify_success(asset_upload.get_asset_by_id_and_type( object_id, object_type, asset_type )) @app.route('/replicate-episodes-audio-assets', methods=['POST']) @validate_request_data(schema.ReplicateEpisodesAudioAssetsSchema()) def replicate_episodes_audio_assets(data): """Post replicate episodes audio assets endpoint. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_upload.replicate_episodes_audio_assets( data.get('objects'))) @app.route('/upload-token', methods=['GET']) def upload_token(): """Get upload token. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(upload_token_logic.get_upload_permission()) @app.route('/upload', methods=['POST']) @validate_request_data(schema.AssetUploadPayloadSchema()) def post_asset(data): """Post asset endpoint. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_upload.update_asset_upload(data)) @app.route('/commit/', methods=['POST']) @validate_request_data(schema.CommitAssetSchema()) def commit_asset(filename, data): """Commit asset endpoint. This does not ownership check so that should be done from calling microservice. Returns: flask.Response: Response containing success or error message. """ object_id = data['object_id'] return responsify_and_flaskify_success(asset_upload.commit(filename, object_id)) @app.route('/upload', methods=['DELETE']) @validate_request_query(schema.AssetDeletePayloadSchema()) def delete_asset(data): """Delete asset upload endpoint. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_upload.delete_asset_upload( frontend_asset_type=data['asset_type'], object_id=data['object_id'], object_type=data['object_type'] )) @app.route('/status', methods=['POST']) @validate_request_data(schema.PostAssetGeneralStatusSchema()) def post_asset_general_status(data): """Update general status of asset processing. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_status.create_status( filename=data.get('filename'), status=data.get('status'), errors=data.get('errors'), timestamp=data.get('timestamp') )) @app.route('/final', methods=['POST']) @validate_request_data(schema.PostAssetFinalSchema()) def post_asset_final(data): """Update encoding status and save info about final (encoded) asset. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_final.create_status_and_final_assets( filename=data.get('filename'), status=data.get('status'), errors=data.get('errors'), timestamp=data.get('timestamp'), final_assets=data.get('final_assets', []) )) @app.route('/status/', methods=['GET']) @validate_request_query(schema.GetAssetsStatusSchema()) def get_asset_status(filename, data): """Return status of asset processing. Args: filename (str): Unique filename with extension. Returns: flask.Response: Response containing success or error message. """ signed_url_policy = data.get('signed_url_policy', urls.SIGNED_URL) signed_url_duration = data.get('signed_url_duration', urls.DEFAULT_SIGNED_URL_DURATION) return responsify_and_flaskify_success( asset_status.get_status( filename=filename, signed_url_policy=signed_url_policy, signed_url_duration=signed_url_duration)) @app.route('/replicate-podcast-artwork-asset', methods=['POST']) @validate_request_data(schema.PodcastArtworkAssetSchema()) def replicate_podcast_artwork_asset(data): """Post replicate podcast artwork asset endpoint. Returns: flask.Response: Response containing success or error message. """ return responsify_and_flaskify_success(asset_upload.replicate_podcast_artwork_asset(data)) @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): g.log.exception(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.') g.log.exception(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) @app.before_request @request_context_from_headers() def before_request(): """Code to be executed before each request.""" headers = request.headers g.client_ip = headers.get(urls.IP_ADDR_HEADER, config.LOCAL_CLIENT_IP)