"""Track handlers.""" from connector_neo4j import Neo4jSession from flask import g from flask import request from jsonschema import validate, ValidationError from oto import response from oto.adaptors.flask import flaskify from owsrequest.constants import headers as header_constants from owsrequest import flask_request from owsresponse import response as ows_response from owsresponse.adaptors.flask import flaskify as ows_flaskify from backend import config from backend.api import app from backend.constants import api from backend.constants import error from backend.constants import header as header_const from backend.enums import query from backend.logic import isrc as isrc_logic from backend.logic import track as track_logic from backend.logic import track_vendor as track_vendor_logic from backend.logic import track_localization as track_localization_logic from backend.logic import track_role as track_role_logic from backend.logic import track_spatial as track_spatial_logic from backend.schemas import track as track_schemas from backend.schemas import track_spatial as track_spatial_schemas from backend.schemas.order_by_fields import OrderByFieldSchema from backend.schemas.track_localization import TrackLocalizationSchema from backend.utils import api as api_utils from backend.utils import handlers as handlers_util from backend.validation import api_schema_validators @app.route('/track', methods=['POST']) @handlers_util.get_account_from_grass def create_track(account): """Create a new track. Args: account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 201 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_schemas.TrackPostSchema) result = track_logic.create( track_data=json_data, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track/upc//isrc/', methods=['GET']) def get_track_by_upc_and_isrc(upc, isrc): """Fetch a track given upc and isrc. Args: upc (int): UPC of the track's product. isrc (str): ISRC of the track. Returns: flask.Response: on successful, 200 status with JSON body containing full track data. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: jwt_identity_id = g.request_context.jwt_identity_id if not jwt_identity_id: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if not handlers_util.is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( code=error.OWS_PERMISSIONS_ERROR_CODE, message=error.OWS_PRODUCT_FORBIDDEN_MESSAGE, status=403 )) result = track_logic.get_track_by_upc_and_isrc(upc=upc, isrc=isrc) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/product//tracks', methods=['POST']) @handlers_util.get_account_from_grass def bulk_create_tracks(product_id, account): """Create multiple new tracks. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 201 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) for_volume = request.args.get('for_volume') if for_volume: if for_volume.isdigit(): for_volume = int(for_volume) else: msg = error.VALIDATION_ERROR_OPTIONAL_INT_MSG.format('for_volume') return flaskify(api_utils.create_validation_error_response(msg)) result = track_logic.bulk_create( product_id=product_id, track_data_list=request.get_json(), for_volume=for_volume, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/product//tracks/import', methods=['POST']) @handlers_util.get_account_from_grass def bulk_import_tracks(product_id, account): """Import existing tracks by copying all of the associated metadata. Sample request body: { "tuids": [1, 2, 3] } Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body """ correlation_id = request.headers.get(header_const.CORRELATION_ID) orchard_user_id = request.headers.get(header_const.ORCHARD_USER_ID) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.import_tracks( request_data=request.get_json(), product_id=product_id, orchard_user_id=orchard_user_id, account_type=account.type, account_id=account.id, correlation_id=correlation_id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/product//tracks/change_genre', methods=['POST']) @handlers_util.get_account_from_grass @handlers_util.get_user_from_grass @handlers_util.get_user_from_profile_headers def update_artist(product_id, account, user, profile): """Update track artists roles upon switching genre and sub-genres. Sample request body: { "genre_id": 12, "subgenre_id": 266 } Args: product_id (int): Unique key of product. account (namedtuple): Account data from GRASS headers. user(namedtuple): User data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body """ access_rule_decision = flask_request.verify_rules_access_standalone(request) request_context = getattr(g, 'request_context', None) jwt_identity_id = getattr(request_context, 'jwt_identity_id', None) if not access_rule_decision: if not jwt_identity_id: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if not handlers_util.is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( code=error.OWS_PERMISSIONS_ERROR_CODE, message=error.OWS_PRODUCT_FORBIDDEN_MESSAGE, status=403, )) correlation_id = request.headers.get(header_const.CORRELATION_ID) orchard_user_id = None if user.type and user.id: orchard_user_id = '{}:{}'.format(user.type, user.id) elif profile.type and profile.id: orchard_user_id = '{}:{}'.format(profile.type, profile.id) elif jwt_identity_id: orchard_user_id = ( request.headers.get(header_const.ORCHARD_IDENTITY_ID) or jwt_identity_id ) if not orchard_user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_MISSING_QUERY_PARAM, message=error.ERROR_MESSAGE_MISSING_ORCHARD_USER_ID)) result = track_role_logic.update_artist( product_id=product_id, request_data=request.get_json(), account_type=account.type, account_id=account.id, correlation_id=correlation_id, orchard_user_id=orchard_user_id) return flaskify(result) @app.route( '/product//tracks/copy/', methods=['POST']) @handlers_util.get_account_from_grass def import_product_tracks(src_product_id, dest_product_id, account): """Copy tracks for newly created digital product. Args: src_product_id (int): The product primary key to copy tracks from. dest_product_id (int): The product primary key to copy tracks to. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: 201 status on success. """ correlation_id = request.headers.get(header_const.CORRELATION_ID) orchard_user_id = request.args.get(api.ORCHARD_USER_ID) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if not orchard_user_id: return flaskify(response.create_error_response( code=error.ERROR_CODE_MISSING_QUERY_PARAM, message=error.ERROR_MESSAGE_MISSING_ORCHARD_USER_ID)) if account.type or account.id: return flaskify(response.create_error_response( code=error.ERROR_CODE_INVALID_HEADER, message=error.ERROR_MESSAGE_CALL_NOT_ALLOWED)) track_list = None if request.is_json: track_list = request.get_json().get('track_list') result = track_logic.copy_tracks_for_product( product_id=src_product_id, new_product_id=dest_product_id, orchard_user_id=orchard_user_id, correlation_id=correlation_id, track_list=track_list ) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track/', methods=['GET']) @handlers_util.get_account_from_grass def get_track(tuid, account): """Fetch an existing track. Args: tuid (int): The track primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ exclude_fields = request.args.get('exclude', '').lower().split(',') include_fields = request.args.get('include', '').lower().split(',') access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.get_by_tuid( tuid=tuid, account_type=account.type, account_id=account.id, exclude=exclude_fields, include=include_fields, ) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/tracks', methods=['GET']) def get_multiple_tracks(): """Fetch multiple tracks. This is meant to only be a microservice to microservice call. No ownership checks are done. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) try: tuids = handlers_util.explode_query_param_to_ids('tuids') except ValueError as e: return flaskify(api_utils.create_validation_error_response(str(e))) exclude_fields = request.args.get('exclude', '').lower().split(',') result = track_logic.get_multiple_by_tuids(tuids, exclude_fields) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/tracks-bulk', methods=['GET']) def get_multiple_bulk_tracks(): """Fetch bulk tracks for provided product_ids with limited fields. This is meant to only be a microservice to microservice call. Returns: flask.Response: on successful, 200 status with JSON body. """ try: product_ids = handlers_util.explode_query_param_to_ids('product_ids') access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: jwt_identity_id = g.request_context.jwt_identity_id if not jwt_identity_id: return flaskify(response.create_error_response( error.BAD_REQUEST_CODE, error.ERROR_CODE_AUTHORIZATION)) if not handlers_util.is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( error.OWS_PERMISSIONS_ERROR_CODE, error.OWS_PRODUCT_FORBIDDEN_MESSAGE, status=403)) validate( instance=product_ids, schema={ '$schema': 'https://json-schema.org/draft/2020-12/schema', 'type': 'array', 'uniqueItems': True, 'minItems': 1, 'maxItems': 185, 'items': { 'type': 'integer', 'minimum': 1 }, } ) except (ValueError, ValidationError) as ve: return flaskify( ows_response.create_error_response( code=error.ERROR_CODE_QUERY_VALIDATION, message=str(ve), ), ) result = track_logic.get_all_tracks_by_product_ids_medium( product_ids=product_ids ) return ows_flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track-wide-dataloader', methods=['POST']) def get_tracks_dataloaded(): """Fetch tracks dataloaded. This is meant to only be a microservice to microservice call. No ownership checks are done. This endpoint returns the tracks in order of the tuids provided, and has nulls where the tuids don't exist. This endpoint returns a lot of data about tracks instead of just the main data, and should be considered deprecated. Returns: flask.Response: on successful, 200 status with JSON body. """ tuids = request.json.get('tuids') access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.get_multiple_by_tuids_with_nones(tuids) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/product//tracks', methods=['GET']) @handlers_util.get_account_from_grass def get_all_tracks_by_product_id(product_id, account): """Get a list of tracks by product_id. Get all the tracks contained in a product using product_id (release_id in AR) as the identifier. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body Track information related to the given product_id. """ exclude_fields = request.args.get('exclude', '').lower().split(',') is_overview = request.args.get('is_overview', False) access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.get_all_tracks_by_product_id( product_id=product_id, account_type=account.type, account_id=account.id, exclude=exclude_fields, is_overview=is_overview) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/products/trackids', methods=['POST']) @handlers_util.get_account_from_grass def get_tracks_ids_by_product_ids(account): """Get a list of tracks IDs by product_id. Get all the tracks contained in a product using product_id (release_id in AR) as the identifier. Args: account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body Track information related to the given product_id. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) # 1: Validate product_ids parameter in request body product_ids = request.json.get('product_ids', []) if not isinstance(product_ids, list): raise TypeError( f'product_ids must be lists, but it is {type(product_ids)}', ) # 2: Validate order_by_fields parameter in request body order_by_fields = request.json.get('order_by_fields') # 2.1: Default sorting order when no order_by_fields in request if not order_by_fields: order_by_fields = [ { 'column_name': 'volume_number', 'order': query.SortingOrderEnum.ASC, }, { 'column_name': 'track_number', 'order': query.SortingOrderEnum.ASC, }, ] # 2.2: Check if order_by_fields is a list if not isinstance(order_by_fields, list): raise TypeError( 'order_by_fields must be lists, but ' f'it is {type(order_by_fields)}', ) # 2.3: Prepare order_by_fields for the query order_by_schema = OrderByFieldSchema() formatted_order_by_fields = [ order_by_schema.load( {'column_name': f} if isinstance(f, str) else f, ).data for f in order_by_fields ] # 3: Calculate the request result result = track_logic.get_tracks_ids_by_product_ids_with_order( product_ids=product_ids, account_type=account.type, account_id=account.id, order_by_fields=formatted_order_by_fields, ) return flaskify(result) @app.route('/product//tracks/light', methods=['GET']) @handlers_util.get_account_from_grass def get_all_tracks_by_product_id_light(product_id, account): """Get a list of tracks by product_id. Get all the tracks contained in a product using product_id (release_id in AR) as the identifier. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body Track information related to the given product_id. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.get_all_tracks_by_product_id_light( product_id=product_id, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/track/', methods=['PUT']) @handlers_util.get_account_from_grass @handlers_util.get_user_from_grass def update_track(tuid, account, user): """Update track. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_schemas.TrackUpdateSchema) # Determine if a isrc must be generated generate_isrc = bool(int(request.args.get('generate_isrc', 0))) result = track_logic.update( tuid=tuid, data=json_data, generate_isrc=generate_isrc, account_type=account.type, account_id=account.id, user_type=user.type if user else None, user_id=user.id if user else None) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track/', methods=['PATCH']) @handlers_util.get_account_from_grass @handlers_util.get_user_from_grass def update_track_patch(tuid, account, user): """Update track, but only for fields passed in. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_schemas.TrackUpdateSchema, partial=True) # Determine if a isrc must be generated generate_isrc = bool(int(request.args.get('generate_isrc', 0))) # Determine if we are allowed to set artist_info_ids without track_artist get_artist_info_ids = bool( int(request.args.get('get_artist_info_ids_from_existing_rows', 0))) result = track_logic.update( tuid=tuid, data=json_data, patch_response=True, generate_isrc=generate_isrc, account_type=account.type, account_id=account.id, user_type=user.type if user else None, user_id=user.id if user else None, get_artist_info_ids=get_artist_info_ids) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track//duration', methods=['PATCH']) @handlers_util.get_account_from_grass @handlers_util.get_user_from_grass def update_track_duration(tuid, account, user): """Update track duration fields. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_schemas.TrackDurationSchema, partial=True) result = track_logic.update_track_duration( tuid=tuid, data=json_data, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/product//tracks', methods=['PATCH']) @handlers_util.get_account_from_grass @handlers_util.validate_body( api_schema_validators.PATCH_PRODUCT_TRACKS_BODY_VALIDATOR) def update_field_for_all_tracks_in_product(product_id, account): """Update all tracks for product for a field. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body """ # Determine if a isrc must be generated access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) generate_isrc = bool(request.args.get('generate_isrc', 0, int)) # Request data can be empty if it's a request to generate ISRCs request_data = request.get_json(silent=True) or {} result = track_logic.update_field_for_all_tracks_in_product( product_id=product_id, data=request_data, account_type=account.type, account_id=account.id, generate_isrc=generate_isrc) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/product//tracks/arrange', methods=['PUT']) @handlers_util.get_account_from_grass def reorder_tracks(product_id, account): """Reorder tracks for product. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.reorder( product_id=product_id, arrangement=request.get_json(), account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/track/', methods=['DELETE']) @handlers_util.get_account_from_grass def delete_track(tuid, account): """Delete track. Args: tuid (int): The track primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.delete( tuid=tuid, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/product//tracks', methods=['DELETE']) @handlers_util.get_account_from_grass def bulk_delete_tracks(product_id, account): """Delete multiple tracks. Sample request body: { "tuids": [1, 2, 3] } Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) try: tuids = handlers_util.explode_query_param_to_ids('tuids') except ValueError as e: return flaskify(api_utils.create_validation_error_response(str(e))) result = track_logic.bulk_delete( tuids=tuids, product_id=product_id, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/track//', methods=['POST']) @handlers_util.get_account_from_grass def create_track_role(tuid, role, account): """Create track role. Args: tuid (int): The track primary key. role (str): the role to create. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 201 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_role_logic.create( tuid=tuid, role=role.lower(), data=request.get_json(), account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/track///', methods=['PATCH']) @handlers_util.get_account_from_grass def update_track_role(tuid, role, role_id, account): """Update track role. Args: tuid (int): The track primary key. role (str): The role to update. role_id (int): The primary key of the track role Returns: flask.Response on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_role_logic.update( tuid=tuid, role=role.lower(), role_id=role_id, data=request.get_json(), account_type=account.type, account_id=account.id ) return flaskify(result) @app.route('/product//tracks/', methods=['PATCH']) @handlers_util.get_account_from_grass def update_roles_for_all_tracks_in_product(product_id, role_type, account): """Update all tracks for a product for specified role_type. Args: product_id (int): The product primary key. role_type (str): The role_type to update. account (namedtuple): Account data from GRASS headers. Returns: flask.response: on successful, 200 status with JSON body """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) request_data = request.get_json() result = track_role_logic.update_for_all_tracks_in_product( product_id=product_id, role_type=role_type, data=request_data, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/track///', methods=['DELETE']) @handlers_util.get_account_from_grass def delete_track_role(tuid, role_type, track_role_id, account): """Delete track role. Args: tuid (int): The track primary key. role_type (str): The role_type to update. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_role_logic.delete( tuid=tuid, role=role_type.lower(), track_role_id=track_role_id, account_type=account.type, account_id=account.id) return flaskify(result) @app.route( '/track//localization/', methods=['PUT']) @handlers_util.get_account_from_grass def update_track_localization(tuid, language_id, account): """Create or update track localization. Args: tuid (int): The track primary key. language_id (int): The language primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=TrackLocalizationSchema) result = track_localization_logic.update( tuid=tuid, language_id=language_id, localization_data=json_data, account_type=account.type, account_id=account.id) return flaskify(result) @app.route( '/track//localization/', methods=['DELETE']) @handlers_util.get_account_from_grass def delete_track_localization(tuid, language_id, account): """Delete track localization. Args: tuid (int): The track primary key. language_id (int): The language primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_localization_logic.delete( tuid=tuid, language_id=language_id, account_type=account.type, account_id=account.id) return flaskify(result) @app.route('/product//tracks/validate', methods=['GET']) @handlers_util.get_account_from_grass @handlers_util.get_user_from_grass def validate_tracks_for_product(product_id, account, user): """Validate all tracks of a product. Returns the total number of tracks and the number of valid ones. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. user: User data from headers. Returns: flask.response: on successful, 200 status with JSON body Example: { "total_tracks": 10, "valid_tracks": 8, "errors": {...} } """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) validation_contexts = { 'pre_submission', 'post_submission', } profile_type = '' validation_context = request.args.get( 'validation_context') or 'pre_submission' is_known_validation_context = ( validation_context and validation_context in validation_contexts) if not is_known_validation_context: raise Exception('Unknown validation context.') elif validation_context == 'post_submission': has_access = g.request_context.profile_type == ( header_constants.PROFILE_TYPE_CONTENT) if not has_access: raise Exception('No access to validation context.') profile_type = header_constants.PROFILE_TYPE_CONTENT identity_id = request.headers.get(header_const.ORCHARD_IDENTITY_ID) result = track_logic.validate_tracks_for_product( product_id=product_id, account_type=account.type, account_id=account.id, user_type=user.type, user_id=user.id, validation_context=validation_context, profile_type=profile_type, identity_id=identity_id ) return flaskify(result) @app.route('/vendor/tracks/validate/dataloader', methods=['POST']) def dataload_validate_tracks_for_vendor(): """Validate all tracks on products from a vendor. Returns all tracks that have ISRC/OSR mismatches. Args: vendor_id (int): The vendor that the products belong to. Returns: flask.response: on successful, 200 status with JSON body Example: { "total_products": 10, "products_with_track_issues": 8, "products": [ { "product_id": 1111111, "track_tuid": 11111111, "track_isrc": "ABC11111111", "warning_type": "Cross Track ISRC Mismatch", "match_tuid": 222222222, "match_isrc": "ABC22222222", "match_product_id": 2222222, "match_upc": "100100100100", "match_vendor_id": 25824 }, ... ] } """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) payload = request.get_json() if ( not isinstance(payload, dict) or not payload.get('product_ids') or not all(isinstance(x, int) for x in payload.get('product_ids', [])) ): return flaskify( response.create_error_response( error.ERROR_CODE_INVALID_INPUT, error.ERROR_MESSAGE_INVALID_DATALOAD, status=400, ) ) product_ids = payload.get('product_ids', []) result = track_vendor_logic.dataload_tracks_isrc_validation( product_ids=product_ids ) return flaskify(result) @app.route('/isrc//', methods=['GET']) def is_isrc_in_use(track_type, isrc): """Determine if an isrc is in use. track_type can be either 'music' or 'video'. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) return flaskify( isrc_logic.is_isrc_in_use( track_type, isrc ) ) @app.route( '/track//', methods=['GET']) def get_all_tracks_by_type_and_isrc(track_type, isrc): """Retrieve all tracks based on the type and ISRC. Args: track_type (str): type of the track isrc (str): ISRC of the track Returns: flask.response: list of tracks """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) return flaskify( track_logic.get_all_tracks_by_type_and_isrc( track_type, isrc ) ) @app.route('///tracks//', methods=['GET']) @handlers_util.get_account_from_grass def get_account_tracks_by_type_and_isrc(account_type, account_id, track_type, isrc, account): """Retrieve all of an account's tracks based on the type and ISRC. Args: account_type (str): the type of the account, either vendor or subaccount account_id (int): the id of the account track_type (str): type of the track isrc (str): ISRC of the track account (namedtuple): Account data from GRASS headers. Returns: flask.response: list of tracks """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if account.type or account.id: response_object = response.create_fatal_response( message=error.ERROR_CODE_BAD_GRASS_REQUEST) return flaskify(response_object) return flaskify( track_logic.get_account_tracks_by_type_and_isrc( account_type, account_id, track_type, isrc, ) ) @app.route( '/product//tracks', methods=['PUT']) @handlers_util.get_account_from_grass def create_tracks_with_metadata(product_id, account): """Create multiple tracks and populate them with metadata. Args: product_id (int): Product id. account (namedtuple): Account data from GRASS headers. Returns: flask.response: List of created tracks. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_schemas.TracksCreateWithMetadata) return flaskify( track_logic.bulk_create_track_with_metadata( product_id, json_data, account_type=account.type, account_id=account.id), encoder=handlers_util.DatetimeEncoder) @app.route( '/product//tracks/set', methods=['PUT']) @handlers_util.get_account_from_grass def set_product_tracks(product_id, account): """Set the tracks on a product so they match the payload exactly. This endpoint will create, update and delete tracks on a product. Args: product_id (int): Product id. account (namedtuple): Account data from GRASS headers. Returns: flask.response: List of tracks on the product after all the changes. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) data = handlers_util.parse_request_json( schema=track_schemas.SetProductTracksSchema) # Generate ISRC unless the payload has generate_isrc=False gen_isrc = data.get('generate_isrc') or data.get('generate_isrc') is None return flaskify( track_logic.set_product_tracks( product_id, data['tracks'], account_type=account.type, account_id=account.id, generate_isrc=gen_isrc, ), encoder=handlers_util.DatetimeEncoder) @app.route('/track-artist//track', methods=['GET']) @handlers_util.get_account_from_grass def get_track_by_track_artist_id(track_artist_id, account): """Fetch an existing track by track_artist_id. Args: track_artist_id (int): Track artist id. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if account.type or account.id: response_object = response.create_fatal_response( message=error.ERROR_CODE_BAD_GRASS_REQUEST) return flaskify(response_object) result = track_logic.get_track_by_track_artist_id(track_artist_id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/claim_new_isrc', methods=['POST']) def claim_new_isrc(): """Claim an ISRC. Returns: flask.Response: Contains status code and payload with ISRC (str). """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_logic.claim_new_isrc() return flaskify(result) @app.route('/isrc//matches', methods=['GET']) def isrc_match_info(isrc): """Get a paginated list of tracks that match the given ISRC. @TODO Add permissions check. Returns: flask.Response: on successful, 200 status with JSON body containing pagination.total_records a page of tracks. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) limit = request.args.get('limit', 10) offset = request.args.get('offset', 0) result = isrc_logic.get_tracks_with_isrc_logic(isrc, int(limit), int(offset)) return flaskify(response.Response( message=result, status=200)) @app.route('/profile/uuid//resource/track/id/', methods=['HEAD']) def check_profile_track_access(profile_uuid, tuid): """Check profile access to a track.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) return flaskify(track_logic.check_profile_track_access(profile_uuid, tuid)) @Neo4jSession(use_v2=True, database=config.NEO4J_DATABASE_NAME) @app.route('/tracks/osr_id/', methods=['GET']) def get_tracks_by_osrid(osr_id): """Get a paginated list of tracks by OrchardSoundRecording ID. Returns: flask.Response: on successful, 200 status with JSON body containing paginated track items and total tracks. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) limit = request.args.get('limit', 20) offset = request.args.get('offset', 0) result = track_logic.get_tracks_by_osrid(osr_id, int(limit), int(offset)) return flaskify(response.Response( message=result, status=200)) @app.route('/track//spatial', methods=['GET']) @handlers_util.get_account_from_grass def get_track_spatial(tuid, account): """Get track spatial data. Args: tuid (int): The track primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on success, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_spatial_logic.get_track_spatial( tuid=tuid, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/products//tracks/spatial', methods=['GET']) @handlers_util.get_account_from_grass def get_spatial_isrc_map_by_product_id(product_id, account): """Get spatial ISRC mapping for all tracks in a product. Args: product_id (int): The product primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: on success, 200 status with JSON body containing a dict of {track_id: isrc}. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_spatial_logic.get_spatial_isrc_map_by_product_id( product_id=product_id, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track//spatial', methods=['POST']) @handlers_util.get_account_from_grass def create_track_spatial(tuid, account): """Create new track spatial. Args: tuid (int): The track primary key. account (namedtuple): Account data from GRASS headers. Returns: flask.Response: 200 with JSON body on success, 409 if spatial already exists for this track. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: jwt_identity_id = g.request_context.jwt_identity_id if not jwt_identity_id: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) if not handlers_util.is_jwt_identity_authorized(jwt_identity_id): return flaskify(response.create_error_response( code=error.OWS_PERMISSIONS_ERROR_CODE, message=error.OWS_PRODUCT_FORBIDDEN_MESSAGE, status=403 )) json_data = handlers_util.parse_request_json( schema=track_spatial_schemas.TrackSpatialPostSchema) result = track_spatial_logic.create_track_spatial( tuid=tuid, data=json_data, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track//spatial', methods=['PUT']) @handlers_util.get_account_from_grass def update_track_spatial(tuid, account): """Update track spatial. Args: tuid (int): The track primary key. Returns: flask.Response on successful, 200 status with JSON body. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) json_data = handlers_util.parse_request_json( schema=track_spatial_schemas.TrackSpatialUpdateSchema) result = track_spatial_logic.update_track_spatial( tuid=tuid, data=json_data, account_type=account.type, account_id=account.id) return flaskify(result, encoder=handlers_util.DatetimeEncoder) @app.route('/track//spatial', methods=['DELETE']) @handlers_util.get_account_from_grass def delete_track_spatial(tuid, account): """Update track spatial. Args: tuid (int): The track primary key. Returns: flask.Response on successful, 200 status. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify(ows_response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, )) result = track_spatial_logic.delete_track_spatial( tuid=tuid, account_type=account.type, account_id=account.id) return flaskify(result)