"""Logic for Participant. This module holds the logic for performing actions against Participant entities. """ from typing import List from uuid import UUID import participant.constants.label_participant_queries as q from flask import g from neobolt.exceptions import ConstraintError from owsresponse import response from participant.constants import error, relationships from participant.models import artist_name_updates from participant.models import base as base_models from participant.utils.convert import escape_term, format_response, to_int from participant.utils.dataloader import format_for_dataloader def get_label_participant_by_id(label_participant_id): """Get a label participant by id. Args: label_participant_id (int): The label participant's unique identifier. Returns: response.Response: containing a label participant record. """ if hasattr(g, 'vendor_id'): search_subaccount_clause = q.subaccount_clause if g.subaccount_id else '' result = base_models.get_node( q.get_label_participant_by_id.format( subaccount_clause=search_subaccount_clause ), id=label_participant_id, vendor_id=g.vendor_id, subaccount_id=g.subaccount_id, ) elif hasattr(g, 'artist_info_resources'): result = base_models.get_node( q.get_label_participant_by_id_and_resources, id=label_participant_id, artist_info_resources=g.artist_info_resources, ) else: result = base_models.get_node( q.get_label_participant_by_id_only, id=label_participant_id, ) if not result: return response.create_not_found_response('Resource not found.') return response.Response(format_response(result)) def search_label_participant(params, role=None): """Search for a Label Participant by name. Args: params (dict): search parameters role (str): the role filter Returns: Response: the label participant info. """ search_subaccount_clause = q.subaccount_clause if params['subaccount_id'] else '' search_role_clause = q.role_clause if role else '' term = escape_term(params['name']) term = term.strip() results = base_models.get_nodes( q.search_label_participant.format( subaccount_clause=search_subaccount_clause, role_clause=search_role_clause ), wildcard_query=f'{term}*', fuzzy_query=f'{term}~', exact_query=f'"{term}"', default_query=f'{term}', vendor_id=params['vendor_id'], subaccount_id=params['subaccount_id'], ) return response.Response(format_response(results)) def create_label_participant( name, vendor_id, subaccount_id, spotify_id, apple_music_id ): """Create Label Participant node in neo4j. Args: name (str): Label Participant name. vendor_id (int): Label Participant's vendor id. subaccount_id (int): Label Participant subaccount id. spotify_id (str): Spotify identifier. Optional. apple_music_id (str): Apple Music identifier. Optional. Returns: response.Response: Response with newly created participant. Or error response if a participant with the passed parameters already exists. """ try: result = base_models.create_node( q.create_label_participant, name=name, vendor_id=vendor_id, subaccount_id=subaccount_id, spotify_id=spotify_id, apple_music_id=apple_music_id, ) except ConstraintError as err: return response.create_error_response( error.ERROR_CODE_DUPLICATE_NODE, message=str(err) ) return response.Response(format_response(result)) def get_label_participants_by_related_product_id(product_id, vendor_id, subaccount_id): """Get label participants by related product id.""" results = base_models.get_nodes_and_relationship( q.get_label_participants_by_related_product_id, product_id=product_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) participations = [] for result in results: label_participant = result[0] relationship = result[1] participations.append( { 'role': relationship.get('participated_as'), 'labelParticipant': { 'id': label_participant.get('id'), 'name': label_participant.get('name'), 'vendorId': label_participant.get('vendorId'), 'subaccountId': label_participant.get('subaccountId'), 'appleMusicId': label_participant.get('appleMusicId'), 'spotifyId': label_participant.get('spotifyId'), }, } ) return response.Response(format_response(participations)) def get_label_participants_by_related_track_id(track_id): """Get a participant by related track id.""" results = base_models.get_nodes( q.get_label_participants_by_related_track_id, id=track_id, ) return response.Response(format_response(results)) def create_relationship(from_node, to_node, relationship): """Create relationship between two nodes. Args: from_node (dict): From node data. to_node (dict): To node data. relationship (dict): Relationship data. Returns: response.Response: Response of newly created relationship or error. """ from_node_label = from_node.get('label') from_node_id = from_node.get('id') to_node_label = to_node.get('label') to_node_id = to_node.get('id') relationship_name = relationship.get('name') relationship_property = relationship.get('property') relationship_value = relationship.get('value') if not ( from_node_label and from_node_id and to_node_label and to_node_id and relationship_name and relationship_property and relationship_value ): return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) validated_relationship_data = validate_relationship_data( from_node_label, to_node_label, relationship_name, relationship_property, relationship_value, ) if not validated_relationship_data: return validated_relationship_data formatted_query = q.create_relationship.format( from_node_label=from_node_label, to_node_label=to_node_label, relationship_name=relationship_name, relationship_property=relationship_property, ) result = base_models.create_relationship( formatted_query, from_node_id=to_int(from_node_id), to_node_id=to_int(to_node_id), relationship_value=str(relationship_value), vendor_id=g.vendor_id, subaccount_id=g.subaccount_id, ) if not result: return response.create_not_found_response('Could not create resource.') return response.Response(format_response(result)) def update_relationship(from_node, to_node, relationship): """Update relationship between two nodes. Args: from_node (dict): From node data. to_node (dict): To node data. relationship (dict): Relationship data. Returns: response.Response: Response of newly updated relationship or error. """ from_node_label = from_node.get('label') from_node_id = from_node.get('id') to_node_label = to_node.get('label') to_node_id = to_node.get('id') relationship_name = relationship.get('name') relationship_property = relationship.get('property') relationship_value = relationship.get('value') relationship_new_value = relationship.get('new_value') if not ( from_node_label and from_node_id and to_node_label and to_node_id and relationship_name and relationship_property and relationship_value and relationship_new_value ): return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) validated_relationship_data = validate_relationship_data( from_node_label, to_node_label, relationship_name, relationship_property, relationship_new_value, ) if not validated_relationship_data: return validated_relationship_data formatted_query = q.update_relationship.format( from_node_label=from_node_label, to_node_label=to_node_label, relationship_name=relationship_name, relationship_property=relationship_property, ) result = base_models.update_relationship( formatted_query, from_node_id=to_int(from_node_id), to_node_id=to_int(to_node_id), relationship_value=str(relationship_value), relationship_new_value=str(relationship_new_value), vendor_id=g.vendor_id, subaccount_id=g.subaccount_id, ) if not result: return response.create_not_found_response('Could not update resource.') return response.Response(format_response(result)) def delete_relationship(from_node, to_node, relationship): """Delete relationship between two nodes. Args: from_node (dict): From node data. to_node (dict): To node data. relationship (dict): Relationship data. Returns: response.Response: Response of newly deleted relationship or error. """ from_node_label = from_node.get('label') from_node_id = from_node.get('id') to_node_label = to_node.get('label') to_node_id = to_node.get('id') relationship_name = relationship.get('name') relationship_property = relationship.get('property') relationship_value = relationship.get('value') if not ( from_node_label and from_node_id and to_node_label and to_node_id and relationship_name and relationship_property and relationship_value ): return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) validated_relationship_data = validate_relationship_data( from_node_label, to_node_label, relationship_name, relationship_property ) if not validated_relationship_data: return validated_relationship_data formatted_query = q.delete_relationship.format( from_node_label=from_node_label, to_node_label=to_node_label, relationship_name=relationship_name, relationship_property=relationship_property, ) base_models.delete_relationship( formatted_query, from_node_id=to_int(from_node_id), to_node_id=to_int(to_node_id), relationship_value=str(relationship_value), vendor_id=g.vendor_id, subaccount_id=g.subaccount_id, ) return response.Response() def validate_relationship_data( from_node_label, to_node_label, relationship_name, relationship_property, relationship_value=None, ): """Validate or not relationship data provided. Args: from_node_label (str): From node label. to_node_label (str): To node label. relationship_name (str): Relationship name. relationship_property (str): Relationship property. relationship_value (str): Relationship property value. Returns: response.Response: Response if relationship data is valid or not. """ if from_node_label not in relationships.ALLOWED_FROM_NODE_LABELS: return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) if to_node_label not in relationships.ALLOWED_TO_NODE_LABELS: return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) if relationship_name not in relationships.ALLOWED_RELATIONSHIP_NAMES: return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) if relationship_property not in relationships.ALLOWED_RELATIONSHIP_PROPERTIES: return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) if relationship_value: if ( to_node_label in [relationships.TRACK_LABEL, relationships.SOUND_RECORDING_LABEL] and relationship_value not in relationships.ALLOWED_TRACK_RELATIONSHIP_PROPERTY_VALUES ): return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) if ( to_node_label == relationships.PRODUCT_LABEL and relationship_value not in relationships.ALLOWED_RELEASE_RELATIONSHIP_PROPERTY_VALUES ): return response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) return response.Response() def update_artist_name(artist_id, data): """Update artist name. Args: artist_id (int): artist_info identifier. data (dict): data containing artist_name for update and artist_name, request_type and current_artist_id for merge. """ request_type = data.get('request_type') if request_type and request_type == 'merge': current_artist_id = data.get('current_artist_id') return artist_name_updates.merge_artist( current_artist_id, artist_id, data.get('artist_name') ) return artist_name_updates.update_artist_name(artist_id, data.get('artist_name')) def lookup_participants_hierarchy_by_uuids(uuids: List[UUID]) -> response.Response: """Lookup participants' tenant hierarchy by UUIDS. Args: uuids (List[UUID]): List of uuids Returns: response.Response, where message is the list of items """ if not len(uuids): return response.Response({'label_participants': []}) # Neo4J driver does not deal with UUID type, it needs str uuids = [str(uuid) for uuid in uuids] results = base_models.get_records( q.lookup_label_participants_hierarchy_by_uuid, uuids=uuids, ) dataloader_formatted = format_for_dataloader(results, uuids, 'uuid') return response.Response({'label_participants': dataloader_formatted}) def update_lp_and_artist_name(label_participant_uuid, data): """Update lp and artist info name. Args: label_participant_uuid (String): label participant uuid. data (dict): data containing artist_name for update. """ return artist_name_updates.update_lp_and_artist_name( label_participant_uuid, data.get('updated_artist_name') ) def get_products_and_tracks_for_lp(label_participant_uuid): """Get products and tracks for label participant. Args: label_participant_uuid (String): label participant uuid. """ return artist_name_updates.get_products_and_tracks_for_lp(label_participant_uuid) def merge_lp_and_artist_name(data): """Merge lp and artist info name. Args: data (dict): data containing artists for merge. """ return artist_name_updates.merge_lp_and_artist_names( data.get('uuid_1'), data.get('uuid_2'), data.get('merged_artist_uuid') )