"""Logic layer for subscriptions.""" from typing import Any from flask import g from owsresponse import response, status as ows_status from notifications.constants.subscriptions import ( MAP_SUBSCRIPTION_APPID_TO_PROFILE_TYPE, PUSH_NOTIFICATION_TYPE, ) from notifications.models import identity, stream, subscriptions def resolve_node_ids(node_type: str, attr: str, attr_values: list[str]) -> list[str]: """Get true ids of nodes using another attribute. Args: node_type (str): Label of node to query attr (str): attribute of node to examine attr_values (list): values of attr to match Response: list: values of id attributes on matching nodes """ return subscriptions.fetch_ids(node_type, attr, attr_values) def create_subscription( # noqa: PLR0913 profile_type: str, profile_id: int, entity_type: str, entity_id: str, relationship: str, automatic: bool, ) -> response.Response: """Create given relationship beteen a Profile and an entity. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. entity_type (str): The entity type to follow entity_id (str): The id of entity to follow. relationship (str): The relationship type automatic (bool): If subscription was created via automated process Returns: Response: 201 created, 200 already exists """ g.log.info( 'Creating relationship', resources=dict( profile_id=profile_id, profile_type=profile_type, entity_type=entity_type, entity_id=entity_id, relationship=relationship, automatic=automatic, ), ) exists_response = subscriptions.has_subscription( profile_type, profile_id, entity_type, entity_id, relationship ) if exists_response: return exists_response deleted_exists_response = subscriptions.has_deleted_subscription( profile_type, profile_id, entity_type, entity_id, relationship ) if deleted_exists_response: graph_model_result = subscriptions.soft_undelete_subscription( profile_type, profile_id, entity_type, entity_id, relationship, automatic ) else: graph_model_result = subscriptions.create_subscription( profile_type, profile_id, entity_type, entity_id, relationship, automatic ) return graph_model_result def soft_delete_subscription( profile_type: str, profile_id: int, entity_type: str, entity_id: str, relationship: str ) -> response.Response: """Change relationship type to have prefix DELETED_. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. entity_type (str): The entity type to follow entity_id (str): The id of entity to follow. relationship (str): The relationship type Returns: Response: 204 deleted, 200 does not exist """ g.log.info( 'Soft deleting relationship', resources=dict( profile_id=profile_id, profile_type=profile_type, entity_type=entity_type, entity_id=entity_id, relationship=relationship, ), ) exists_response = subscriptions.has_subscription( profile_type, profile_id, entity_type, entity_id, relationship ) if not exists_response: return response.Response(status=ows_status.OK) return subscriptions.soft_delete_subscription( profile_type, profile_id, entity_type, entity_id, relationship ) def get_subscriptions( profile_type: str, profile_id: int, entity_type: str, relationship: str, logic_args: dict[str, Any], ) -> response.Response: """Get all the entities that the Profile has given relationship to. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. entity_type (str): The entity type followed relationship (str): The relationship type logic_args (dict): options to modify query Returns: Response: 200, with a list of entities with the relationship """ return subscriptions.get_subscriptions( profile_type, profile_id, entity_type, relationship, logic_args ) def has_subscription( profile_type: str, profile_id: int, entity_type: str, entity_id: str, relationship: str ) -> response.Response: """Check to see if a relationship exists for Profile to entity. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. entity_type (str): The entity type to follow entity_id (str): The id of entity to follow relationship (str): The relationship type Returns: Response: 200 exists, 404 not exists. """ return subscriptions.has_subscription( profile_type, profile_id, entity_type, entity_id, relationship ) def unsubscribe_by_notification_type( profile_type: str, profile_id: int, notification_type: str, feed_type: str, followed_entity: str | None, ) -> response.Response: """Disable subscription for notification type. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. notification_type (str): The type of notification feed_type (str): The name of feed followed_entity (str): The type of event source Returns: Response: 204 modified, 404 not exists. """ g.log.info( 'Disabling subscription for notification type', resources=dict(profile_id=profile_id, profile_type=profile_type, **g.log_tags), ) return subscriptions.soft_delete_notification_subscription( profile_type, profile_id, notification_type, feed_type, followed_entity ) def subscribe_by_notification_type( # noqa: PLR0913 profile_type: str, profile_id: int, notification_type: str, feed_type: str, followed_entity: str | None, undelete: bool, ) -> response.Response: """Enable subscription for notification type. Args: profile_type (str): The type of profile (ArtistInsights, etc). profile_id (int): The id of the profile. notification_type (str): The type of notification feed_type (str): The name of feed followed_entity (str): The type of event source undelete (bool): Re-enable deleted subscription Returns: Response: 201 created, 200 already exists. """ g.log.info( 'Enabling subscription for notification type:', resources=dict( profile_id=profile_id, profile_type=profile_type, undelete=undelete, **g.log_tags ), ) exists_response = subscriptions.has_notification_subscription( profile_type, profile_id, notification_type, feed_type, followed_entity ) if exists_response: return exists_response deleted_exists = subscriptions.has_deleted_notification_subscription( profile_type, profile_id, notification_type, feed_type, followed_entity ) if deleted_exists: if not undelete: return response.Response(status=ows_status.CONFLICT) return subscriptions.soft_undelete_notification_subscription( profile_type, profile_id, notification_type, feed_type, followed_entity ) else: return subscriptions.create_notification_subscription( profile_type, profile_id, notification_type, feed_type, followed_entity ) def subscribe_by_notification_type_gs( profile_type: str, profile_id: str, feed_type: str ) -> response.Response: """Subscribe to notifications using GetStream. Args: profile_type (str): The type of profile (InsightProfile, etc). profile_id (str): The id of the profile. feed_type (str): The name of feed """ stream.subscribe_entity( profile_type=profile_type, profile_id=profile_id, entity_type=feed_type, entity_id='all', # For now, subscribe for all id's feed_group=feed_type, ) return response.Response(status=ows_status.CREATED) def unsubscribe_by_notification_type_gs( profile_type: str, profile_id: str, feed_type: str ) -> response.Response: """Unsubscribe from notifications using GetStream. Args: profile_type (str): The type of profile (InsightProfile, etc). profile_id (str): The id of the profile. feed_type (str): The name of feed """ stream.unsubscribe_entity( profile_type=profile_type, profile_id=profile_id, entity_type=feed_type, entity_id='all', feed_group=feed_type, ) return response.Response(status=ows_status.NO_CONTENT) def subscription_for_ws_user( vend_contact_id: int, feed_type: str, entity_type: str, entity_id: int, toggle_on: bool = True ) -> response.Response: """Update subscriptions for a given identity and subscription_name. Args: vend_contact_id (int): Vend contact id. feed_type (str): The name of feed eg: productApproval entity_type (str): followed entity type. eg: Vendor or Subaccount entity_id (int): id for the followed entity. eg: 7123 toggle_on (bool): id for the followed entity. eg: 7123 Returns: Response: success or error response. """ subscription_obj = subscriptions.get_subscription_by_param( followed_entity=entity_type, feed_type=feed_type ) if not subscription_obj or not subscription_obj.message: return subscription_obj identity_obj = identity.get_identity_for_label_profile(vend_contact_id) if not identity_obj or not identity_obj.message: return identity_obj resource_obj = identity.get_resource_by_id_type(entity_type, entity_id) if not resource_obj or not resource_obj.message: return resource_obj if toggle_on: return subscriptions.edit_subscription_for_identity( identity_obj.message.get('id'), subscription_obj.message.get('name'), 'LabelProfile', follow_all_resources=False, follow_resources=[resource_obj.message.get('uuid')], automatic=False, ) return subscriptions.delete_selected_subscription_for_identity( identity_obj.message.get('id'), subscription_obj.message.get('name'), 'LabelProfile', follow_resources=[resource_obj.message.get('uuid')], ) def edit_subscription_for_identity( # noqa: PLR0913 identity_id: str, subscription_name: str, follow_all_resources: bool, follow_resources: list[str], automatic: bool = False, overwrite: bool = False, ) -> response.Response: """Update subscriptions for a given identity and subscription_name. Args: identity_id (str): Identity UUID. subscription_name (str): name of the subscription. follow_all_resources (bool): indicate if it is ON for all labels. follow_resources (list): List of uuids if it is not follow_all_resources. automatic (bool): If this due to automatic process or explicitly by user. overwrite (bool): Force delete instead of editing. Returns: Response: success or error response. """ subscription = subscriptions.get_subscription_by_param(subscription_name) if not subscription or not subscription.message: return subscription # We need profile type to limit which profiles are subscribed. profile_type = MAP_SUBSCRIPTION_APPID_TO_PROFILE_TYPE[subscription.message['appId']] if follow_all_resources or follow_resources: # toggle ON if overwrite: subscriptions.delete_subscription_for_identity( identity_id, subscription_name, profile_type ) return subscriptions.edit_subscription_for_identity( identity_id, subscription_name, profile_type, follow_all_resources, follow_resources ) # toggle OFF return subscriptions.delete_subscription_for_identity( identity_id, subscription_name, profile_type ) def get_all_notifications_for_profile(profile_type: str, profile_id: int) -> list[dict[str, Any]]: """Get all enabled notifications for a given profile. Args: profile_type (str): Profile type (e.g. LabelProfile). profile_id (int): Profile id. Returns: Response: a list of all active notifications. """ return subscriptions.get_all_notifications_for_profile(profile_type, profile_id) def get_all_notifications_for_profile_gs( profile_type: str, profile_id: int ) -> list[dict[str, Any]]: """Get all enabled notifications for a given profile from GetStream. Args: profile_type (str): Profile type (e.g. LabelProfile). profile_id (int): Profile id. Returns: Response: a list of all active notifications. """ entities = stream.get_subscribed_entities(profile_type, profile_id) result = [] for sub in entities['results']: result.append( { 'notification_type': PUSH_NOTIFICATION_TYPE, 'feed_type': sub['target_id'].split(':')[0], 'followed_entity': 'store', } ) return result def get_all_subscriptions_for_identity( identity_id: str, profile_types: list[str] | None = None, app_ids: list[str] | None = None ) -> response.Response: """Get all active subscriptions for a given identity. Args: identity_id (str): identity id. profile_types (list): List of profile types to filter. app_ids (list): List of application names to filter only selected subscriptions. Returns: Response: a list of all active subscriptions. """ return subscriptions.get_all_subscriptions_for_identity(identity_id, profile_types, app_ids) def get_all_subscriptions( app_ids: list[str] | None = None, notification_type: str | None = None ) -> response.Response: """Get all subscriptions. Args: app_ids (list): Application names to filter only selected subscriptions. notification_type (str): Type of notification ie. Email only or Push. Returns: Response: a list of all subscriptions. """ return subscriptions.get_all_subscriptions(app_ids, notification_type)