""" 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: masters_registry.response for more details. """ from flask import jsonify from flask import request from oto import response from owsfeatures import features as python_owsfeatures from masters_registry import config from masters_registry import utils from masters_registry.api import app from masters_registry.constant import db_const from masters_registry.constant import error from masters_registry.constant import field_const from masters_registry.logic import bulk_tasks from masters_registry.logic import locks from masters_registry.logic import masters_registry as mrl from masters_registry.logic import ownership as ownership_logic from masters_registry.util import handler_utils from masters_registry.utils import flaskify from masters_registry.utils import get_orchard_user_id from masters_registry.validation import json_schema from masters_registry.validation.schema import add_tuid_body from masters_registry.validation.schema import bulk_lock_body from masters_registry.validation.schema import bulk_remove_ownership from masters_registry.validation.schema import bulk_remove_territories \ as bulk_remove_territories_schema from masters_registry.validation.schema import bulk_unlock_territories_body from masters_registry.validation.schema import header from masters_registry.validation.schema import lock_body from masters_registry.validation.schema import unlock_territories_body @app.route(config.HEALTH_CHECK) def health(): """Check the health of the application. """ return jsonify({'status': 'ok'}) @app.route('/ownership', methods=['GET']) @json_schema.validate_headers(request, header.schema) def ownership(): """GET ownership information by ISRC Args: isrc (str): international standard recording code """ isrc_requested = request.args.get(field_const.ISRC) ownership_response = mrl.get_ownership(isrc_requested) return flaskify(ownership_response) """ POST method added to deal with large amount of ISRCs - SR-754 """ @app.route('/masterrights', methods=['GET', 'POST']) # noqa E302 @json_schema.validate_headers(request, header.schema) def masterrights(): """GET bulk master rights information by ISRC's""" isrcs = request.args.get(field_const.ISRCS) if not isrcs: """If no ISRCs are provided, check if the request body contains them.""" request_json = request.get_json() isrcs = request_json[field_const.ISRCS] if not isrcs: return flaskify(response.create_not_found_response()) else: isrcs = isrcs.split(',') ignore_missing = request.args.get('ignore_missing') if ignore_missing is not None and ignore_missing.lower() == 'true': ignore_missing = True else: ignore_missing = False return flaskify( ownership_logic.bulk_get_ownership(isrcs=isrcs, ignore_missing=ignore_missing)) @app.route('/ownership/isrc//refresh', methods=['PUT']) @json_schema.validate_headers(request, header.schema) @python_owsfeatures.load_features def refresh_ownership(isrc): """PUT current ownership information from the registry to YouTube. Receives ISRC as URL argument and tuid as json in request body. Args: isrc (str): international standard recording code """ track_data = request.get_json() correlation_id = request.headers.get(field_const.CORRELATION_ID) tuid = track_data.get(field_const.TUID) put_success = mrl.refresh_ownership( isrc, tuid, correlation_id) return flaskify(put_success) @app.route('/ownership/isrc//territories', methods=['PUT']) @json_schema.validate_headers(request, header.schema) @python_owsfeatures.load_features def add_ownership(isrc): """PUT ownership information by TUID and territory list. Updates the masters registry audit table with Opcode: ADD for TUID/ territory pairs associated with a given ISRC. Receives ISRC as URL argument, and userID, tuid, and territories as json in request body. Args: isrc (str): international standard recording code """ ownership_data = request.get_json() correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) tuid = ownership_data.get(field_const.TUID) territories = ownership_data.get(field_const.TERRITORIES) put_success = mrl.update_ownership( isrc, tuid, territories, correlation_id, user) return flaskify(put_success) @app.route('/ownership/isrc//territories', methods=['DELETE']) @json_schema.validate_headers(request, header.schema) @python_owsfeatures.load_features def remove_ownership(isrc): """DELETE ownership information by TUID and territory list. Inserts to the masters registry audit table with Opcode: REMOVE for TUID/ territory pairs associated with a given ISRC. Receives ISRC as URL argument, and userID, tuid, and territories as json in request body. Args: isrc (str): international standard recording code """ ownership_data = request.get_json() correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) tuid = ownership_data.get(field_const.TUID) territories = ownership_data.get(field_const.TERRITORIES) remove_success = mrl.remove_ownership( isrc, tuid, territories, correlation_id, user) return flaskify(remove_success) @app.route('/ownership/upcs', methods=['POST']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) def bulk_import_upcs(): """Bulk import UPCs Receives a list of UPCs as json in request body. Maximum 10000 UPCs allowed in one request. Example: { "upcs": ["669910758229", "669910758230", "669910758231"] } """ request_json = request.get_json() upcs = request_json[field_const.UPCS] correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) if not upcs: return flaskify(response.create_error_response( error.MISSING_UPCS_CODE, error.MISSING_UPCS_MESSAGE, 400)) if len(upcs) > config.MAX_UPCS_COUNT: return flaskify(response.create_error_response( error.MAX_UPCS_EXCEEDED_CODE, error.MAX_UPCS_EXCEEDED_MESSAGE, 400)) import_response = mrl.bulk_import_upcs(upcs, correlation_id, user) return flaskify(import_response) @app.route('/ownership/lock/isrc', methods=['POST']) @json_schema.validate_headers(request, header.schema) @json_schema.validate_body(request, lock_body.schema) @python_owsfeatures.load_features def lock_territories(): """Lock territories Receives lock reason, a ISRCS and list of territories as json in request body. Example: { "lock_reason": "lost ownership dispute", "isrc": "GBPS81528956" "territories": ["US", "Canada", "MX"] } """ correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) request_json = request.get_json() reason = request_json[field_const.LOCK_REASON] isrc = request_json[field_const.ISRC] territories = list(set(request_json[field_const.TERRITORIES])) lock_response = locks.lock_territories_with_conflicts( reason, isrc, territories, correlation_id, user) return flaskify(lock_response) @app.route('/ownership/lock/isrcs', methods=['POST']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) @json_schema.validate_body(request, bulk_lock_body.schema) def bulk_lock_territories(): """Bulk lock territories Receives lock reason, a list of ISRCS and list of territories as json in request body. Example: { "lock_reason": "lost ownership dispute", "isrcs": ["GBPS81528956", "USJSJ1014077"], "territories": ["US", "Canada", "MX"] } """ correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) request_json = request.get_json() reason = request_json[field_const.LOCK_REASON] isrcs = list(set(request_json[field_const.ISRCS])) territories = list(set(request_json[field_const.TERRITORIES])) lock_response = locks.bulk_lock_territories( reason, isrcs, territories, correlation_id, user) return flaskify(lock_response) @app.route('/ownership', methods=['POST']) @json_schema.validate_headers(request, header.schema) @json_schema.validate_body(request, add_tuid_body.schema) @python_owsfeatures.load_features def add_tuid_for_territory(): """Allow user to add an individual tuid for a territory Example: { "tuid": 12345, "isrc": "ABC123", "territories": ["US", "Canada", "MX"] } """ correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) request_json = request.get_json() tuid = request_json[field_const.TUID] isrc = request_json[field_const.ISRC] territories = request_json[field_const.TERRITORIES] add_tuid_response = mrl.add_tuid_for_territory( tuid, isrc, territories, correlation_id, user) return flaskify(add_tuid_response) @app.route('/ownership/unlock/isrc', methods=['POST']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) @json_schema.validate_body(request, unlock_territories_body.schema) def unlock_territories(): """Allow user to unlock territories for single ISRC Example: { "lock_reason": "lost ownership dispute", "isrc": "GBPS81528956", "territories": ["US", "Canada", "MX"] } """ correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) request_json = request.get_json() reason = request_json[field_const.LOCK_REASON] isrc = request_json[field_const.ISRC] territories = list(set(request_json[field_const.TERRITORIES])) unlock_territories_response = locks.unlock_territories( correlation_id, user, isrc, territories, reason) return flaskify(unlock_territories_response) @app.route('/ownership/unlock/isrcs', methods=['POST']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) @json_schema.validate_body(request, bulk_unlock_territories_body.schema) def bulk_unlock_territories(): """Allow user to unlock territories Unlock a list of ISRC/territories, unlocking every combination of the two Example: { "isrcs": ["GBPS81528956", "USJSJ1014077"], "territories": ["US", "Canada", "MX"] } """ correlation_id = request.headers.get(field_const.CORRELATION_ID) user = get_orchard_user_id(request) request_json = request.get_json() isrcs = list(set(request_json[field_const.ISRCS])) territories = list(set(request_json[field_const.TERRITORIES])) unlock_territories_response = locks.bulk_unlock_territories( correlation_id, user, isrcs, territories) return flaskify(unlock_territories_response) @app.route('/ownership/conflicts/resolve', methods=['PUT']) @python_owsfeatures.load_features def bulk_resolve_internal_conflicts(): """Remove ownership of multiple tuids from multiple territories. Ownership information is stored in DynamoDB. For each ISRC there is entry with various fields including 'territories'. It is stored like {'territories': {'AF': [{'tuid': 123}]}, other keys}. """ valid_headers, errors = bulk_remove_ownership.HeaderSchema().load( request.headers) if errors: return flaskify(response.create_error_response( error.HEADER_VALIDATION_ERROR, errors)) payload_response = utils.load_json_from_request() if not payload_response: return flaskify(payload_response) valid_data, errors = bulk_remove_ownership.BodySchema().load( payload_response.message) if errors: return flaskify(response.create_error_response( error.BODY_VALIDATION_ERROR, errors)) valid_data.update(valid_headers) result = ownership_logic.bulk_remove_ownership( **valid_data) return flaskify(result) @app.route('/bulk', methods=['GET']) @json_schema.validate_headers(request, header.schema) def get_bulk_tasks(): """Get a list of bulk processing tasks with their statuses Args: num_records (int): number of task statuses to be returned order_by (str): field, tasks ordering by order_direction (str): desc / asc direction of order page_offset (int): number of records to skip """ try: num_records = int(request.args.get( field_const.NUM_RECORDS, config.DEFAULT_BULK_TASKS_NUM_RECORDS)) except ValueError: return flaskify(response.create_error_response( error.INVALID_NUM_RECORDS_CODE, error.INVALID_NUM_RECORDS_MESSAGE, 400)) order_by = request.args.get( field_const.TASK_ORDER_BY, config.TASK_STATUS_DEFAULT_ORDER_BY) if order_by not in db_const.TASK_STATUS_ALLOWED_ORDER_BY: return flaskify(response.create_error_response( error.INVALID_ORDER_BY, error.INVALID_ORDER_BY_MESSAGE, 400)) order_direction = request.args.get( field_const.TASK_ORDER_DIRECTION, config.TASK_STATUS_DEFAULT_ORDER_DIRECTION) if order_direction not in db_const.TASK_STATUS_ALLOWED_ORDER_DIRECTION: return flaskify(response.create_error_response( error.INVALID_ORDER_DIRECTION, error.INVALID_ORDER_DIRECTION_MESSAGE, 400)) try: page_offset = int(request.args.get(field_const.PAGE_OFFSET, 0)) except ValueError: return flaskify(response.create_error_response( error.INVALID_PAGE_OFFSET_CODE, error.INVALID_PAGE_OFFSET_MESSAGE, 400)) tasks_response = bulk_tasks.get_tasks( num_records, order_by, order_direction, page_offset) return flaskify(tasks_response) @app.route('/generate_report', methods=['GET']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) def generate_report(): """Get a generated report Report generate by Task ID from query parameters. Returns: Response contains send_file attachment. """ correlation_id = request.headers.get(field_const.CORRELATION_ID) task_id = request.args.get(field_const.TASK_ID) import_report_type = request.args.get(field_const.REPORT_TYPE, '').lower() response_answer = bulk_tasks.generate_report( task_id, correlation_id=correlation_id, import_report_type=import_report_type, ) return flaskify(response_answer) @app.route('/get_task_context', methods=['GET']) @python_owsfeatures.load_features @json_schema.validate_headers(request, header.schema) def get_task_context(): """Download a CSV report with task parameters. Report with initial data ISRC/UPC. Returns: Response contains send_file attachment. """ correlation_id = request.headers.get(field_const.CORRELATION_ID) task_id = request.args.get(field_const.TASK_ID) import_report_type = request.args.get( field_const.REPORT_TYPE, '').lower() response_answer = bulk_tasks.generate_initial_report( task_id, import_report_type=import_report_type, correlation_id=correlation_id, ) return flaskify(response_answer) @app.route('/ownership/territories', methods=['DELETE']) def bulk_remove_territories(): """Bulk delete a list of territories for ISRC/TUIDs. Returns: Response with celery task ID. """ payload_response = utils.load_json_from_request() if not payload_response: return flaskify(payload_response) valid_data, errors = bulk_remove_territories_schema.BodySchema().load( payload_response.message) if errors: return flaskify(response.create_error_response( error.BODY_VALIDATION_ERROR, errors)) account_response = handler_utils.get_grass_account() if not account_response: return flaskify(account_response) account_type, account_id = account_response.message correlation_id = request.headers.get(field_const.CORRELATION_ID) orchard_user_id = get_orchard_user_id(request) result = ownership_logic.bulk_remove_territories( items=payload_response.message['items'], account_type=account_type, account_id=account_id, correlation_id=correlation_id, orchard_user_id=orchard_user_id) return flaskify(result)