""" 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 flask from flask import g from flask import jsonify from flask import request from oto import response from oto.adaptors.flask import flaskify from owsrequest import flask_request from assets import config from assets.api import app from assets.constants import asset_types from assets.constants import error from assets.constants import field_const from assets.logic import asset_status from assets.logic import asset_upload from assets.logic import correction_images from assets.logic import image_location from assets.logic import generate_upload_data from assets.logic import ownership from assets.logic import post_asset from assets.logic import validate_upc_for_product from assets.logic import validators from assets.logic.legacy import copy_product_assets from assets.logic.legacy import import_asset from assets.logic.legacy import product from assets.logic.legacy import stream_info from assets.logic.legacy import track from assets.logic.legacy import remove_asset from assets.utils import asset_type as asset_type_util from assets.utils import user as user_util from assets.validation import json_schema from assets.validation.schema import body from assets.validation.schema import query @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.errorhandler(500) def exception_handler(error): """Default handler when uncaught exception is raised. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') g.log.exception(error) return flaskify(response.create_fatal_response(message)) @app.route('/upload-token', methods=['GET']) @json_schema.validate_headers(request) def get_upload_token(): """Generate data required for raw assets upload. Returns: flask.Response: Response containing upload data or error. """ user_id = request.headers.get(field_const.ORCHARD_USER_ID) if not user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) return flaskify(generate_upload_data.get_upload_permission(user_id)) @app.route('/asset/status', methods=['GET']) @json_schema.validate_headers(request) @json_schema.validate_query(request, query.get_assets_status_schema) def get_asset_status(): """Return the current status of the asset. Args: filename (str): query parameter that should have string type Returns: flask.Response: Response status of asset """ account_type = request.headers.get(field_const.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(field_const.GRASS_ACCOUNT_ID) filename = request.args.get(field_const.FILENAME) return flaskify(import_asset.get_status_by_filename( filename, account_type, account_id)) @app.route('/asset', methods=['POST']) @json_schema.validate_headers(request) @json_schema.validate_body(request, body.post_asset_schema) def post_asset_handler(): """Save info about uploaded asset. Returns: flask.Response: Response containing upload data or error. """ body = request.get_json(silent=True) user_id = request.headers.get(field_const.ORCHARD_USER_ID) product_id = body.get(field_const.PRODUCT_ID) if user_id.startswith('alw:'): ownership_result = flask_request.verify_grass_ownership( request, ownership.check_ownership, product_id) if not ownership_result: return flaskify(ownership_result) upc = int(body.get(field_const.UPC)) product_upc_response = validate_upc_for_product.validate_upc_for_product( product_id=product_id, upc=upc ) if not product_upc_response: return flaskify(product_upc_response) track_unique_id = body.get(field_const.TRACK_UNIQUE_ID) asset_type = asset_type_util.map_image_asset_type( body.get(field_const.ASSET_TYPE)) if asset_type not in asset_types.IMAGE_FILE_TYPES: product_upc_response = ( validate_upc_for_product.validate_product_track_ownership( product_id=product_id, track_unique_id=track_unique_id)) if not product_upc_response: return flaskify(product_upc_response) return flaskify(post_asset.post_asset( full_user_id=user_id, asset_type=body.get(field_const.ASSET_TYPE), upc=upc, track_unique_id=track_unique_id, product_id=product_id, filename=body.get(field_const.FILENAME), token=body.get(field_const.TOKEN) )) @app.route('/stream/track/', methods=['GET']) @json_schema.validate_headers(request) def get_streaming_link(track_id): """Return an URL for particular asset, which can be used for streaming. Args: track_id (int): query parameter that identify track. Returns: flask.Response: Response stream url, duration in seconds and track_id. """ user_id = request.headers.get(field_const.ORCHARD_USER_ID) if user_id.startswith('alw:'): ownership_result = flask_request.verify_grass_ownership( request, ownership.check_track_ownership, track_id) if not ownership_result: return flaskify(ownership_result) return flaskify( stream_info.get_stream(track_id, user_id)) @app.route('/asset/product/', methods=['GET']) @json_schema.validate_headers(request) def get_product_by_id(product_id): """Return Returns a set of general information for a particular release. Args: product_id (int): url parameter that identify product. Returns: flask.Response: Response product body """ full_user_id = request.headers.get(field_const.ORCHARD_USER_ID) if full_user_id.startswith('alw:'): ownership_result = flask_request.verify_grass_ownership( request, ownership.check_ownership, product_id) if not ownership_result: return flaskify(ownership_result) return flaskify(product.get_product_assets(product_id, full_user_id)) @app.route('/validators/product//bit-depth', methods=['GET']) def validate_bit_depth(product_id): """To check uniform bitrates for provided product. Args: product_id (Integer): Unique identifier of product. """ physical_location_id = request.args.get('physical_location_id', 1) asset_type_id = request.args.get('asset_type_id', 1) return flaskify(validators.validate_bit_depth_by_upc( product_id, physical_location_id, asset_type_id)) @app.route( '/image////location', methods=['GET']) def get_image_location(image_type, image_format, entity_id): """Retrieve the location of an image from the cloudfront cdn. Args: image_type: (String): Type of entity (e.g. "product" or "artist"). image_format: (String): Whether this is for a thumbnail or cover. entity_id (Integer): Unique identifier for a product or artist. """ return flaskify(image_location.get_image_location( entity_id, image_type, image_format)) @app.route( '/validators/product//desired-bit-depth/', methods=['GET']) def check_for_desired_bit_depth(product_id, bit_depth): """To validate bit depth of assets for provided UPC. Args: upc (int): upc to validate bit depth value bit_depth (int): Desired bit-depth value """ physical_location_id = request.args.get('physical_location_id', 1) asset_type_id = request.args.get('asset_type_id', 1) return flaskify(validators.check_for_desired_bit_depth( product_id, bit_depth, physical_location_id, asset_type_id)) @app.route('/track//copy/', methods=['POST']) @json_schema.validate_headers(request, required=False) def copy_track(from_tuid, to_tuid): """Create a copy of the track assets. Args: from_tuid (int): Original track unique id. to_tuid (int): New track unique id. Returns: flask.Response: Response containing copying status or error. """ full_user_id, auth_type = user_util.get_user_id_from_headers_or_body( request) if full_user_id is None: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) if (full_user_id.startswith('alw:') and auth_type == user_util.HEADERS_AUTH): from_track_ownership = flask_request.verify_grass_ownership( request, ownership.check_track_ownership, from_tuid) if not from_track_ownership: return flaskify(from_track_ownership) to_track_ownership = flask_request.verify_grass_ownership( request, ownership.check_track_ownership, to_tuid) if not to_track_ownership: return flaskify(to_track_ownership) return flaskify(track.copy_track_asset( from_tuid, to_tuid, full_user_id)) @app.route('/product//copy/', methods=['POST']) @json_schema.validate_headers(request, required=False) def copy_product(from_pid, to_pid): """Copy assets from source product to destination product. Args: from_pid (int): Source product id. to_pid (int): Destination product id. Returns: flask.Response: Response which contains copy process status or error. """ full_user_id, auth_type = user_util.get_user_id_from_headers_or_body( request) if full_user_id is None: return flaskify(response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Failed to identify user', status=401 )) if (full_user_id.startswith('alw:') and auth_type == user_util.HEADERS_AUTH): ownership_from = flask_request.verify_grass_ownership( request, ownership.check_ownership, from_pid) if not ownership_from: return flaskify(ownership_from) ownership_to = flask_request.verify_grass_ownership( request, ownership.check_ownership, to_pid) if not ownership_to: return flaskify(ownership_to) return flaskify( copy_product_assets.copy_product_assets( from_pid, to_pid, full_user_id)) @app.route('/image/', methods=['DELETE']) @json_schema.validate_headers(request) def remove_product_image(product_id): """Remove a product image. Args: product_id (int): Unique identifier of product. Returns: flask.Response: Response containing image removing status or error. """ full_user_id = request.headers.get(field_const.ORCHARD_USER_ID) if full_user_id.startswith('alw:'): ownership_result = flask_request.verify_grass_ownership( request, ownership.check_ownership, product_id) if not ownership_result: return flaskify(ownership_result) return flaskify(remove_asset.remove_image_asset_by_product_id(product_id)) @app.route('/track/', methods=['DELETE']) @json_schema.validate_headers(request) def remove_track_asset(track_id): """Remove assets for track with track_id. Args: track_id (int): Unique identifier of track. Returns: flask.Response: Response containing image removing status or error. """ full_user_id = request.headers.get(field_const.ORCHARD_USER_ID) if full_user_id.startswith('alw:'): ownership_result = flask_request.verify_grass_ownership( request, ownership.check_track_ownership, track_id) if not ownership_result: return flaskify(ownership_result) return flaskify(remove_asset.remove_track_asset(track_id)) @app.route('/correction/images/approval/', methods=['POST']) @json_schema.validate_headers(request) def rename_correction_images(product_id): """Rename the assets for an error correction image to overwrite originals. Args: product_id (int): Unique identifier of the product. Returns: flask.Response: Response indicating success or error. """ full_user_id = request.headers.get(field_const.ORCHARD_USER_ID) if not full_user_id.startswith('oa:'): return flask.Response(status=403) return flaskify(correction_images.rename_image_assets(product_id)) @app.route('/v2/asset', methods=['POST']) @json_schema.validate_body(request, body.post_asset_schema_v2) def post_asset_handler_v2(): """Save info about uploaded asset from lambda. Returns: flask.Response: Response containing upload data or error. """ body = request.get_json(silent=True) return flaskify(asset_upload.update_asset_upload( asset_type=body.get(field_const.ASSET_TYPE), filename=body.get(field_const.FILENAME), upc=body.get(field_const.UPC), track_unique_id=body.get(field_const.TRACK_UNIQUE_ID), product_id=body.get(field_const.PRODUCT_ID) )) @app.route('/v2/asset/status/', methods=['GET']) @json_schema.validate_headers(request) def get_asset_general_status_v2(filename): """Return general status of asset processing. Args: filename (str): Unique filename with extension. Returns: flask.Response: Response containing success or error message. """ account_type = request.headers.get(field_const.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(field_const.GRASS_ACCOUNT_ID) return flaskify(asset_status.get_current_status( filename, account_type, account_id))