diff --git a/discovery-provider/alembic/trigger_sql/handle_challenge_disbursements.sql b/discovery-provider/alembic/trigger_sql/handle_challenge_disbursements.sql index ab6fe57976b..5c8e8fe5cf7 100644 --- a/discovery-provider/alembic/trigger_sql/handle_challenge_disbursements.sql +++ b/discovery-provider/alembic/trigger_sql/handle_challenge_disbursements.sql @@ -8,14 +8,15 @@ begin if reward_manager_tx is not null then -- create a notification for the challenge disbursement insert into notification - (slot, user_ids, timestamp, type, specifier, data) + (slot, user_ids, timestamp, type, group_id, specifier, data) values ( new.slot, ARRAY [new.user_id], reward_manager_tx.created_at, - 'challenge_reward', - 'challenge_reward:' || new.user_id || ':' || new.specifier, + 'challenge_reward', + 'challenge_reward:' || new.user_id || ':challenge:' || new.challenge_id || ':specifier:' || new.specifier, + new.user_id, json_build_object('specifier', new.specifier, 'challenge_id', new.challenge_id, 'amount', new.amount) ) on conflict do nothing; diff --git a/discovery-provider/alembic/trigger_sql/handle_follow.sql b/discovery-provider/alembic/trigger_sql/handle_follow.sql index abe8e7fcbca..24c0847f191 100644 --- a/discovery-provider/alembic/trigger_sql/handle_follow.sql +++ b/discovery-provider/alembic/trigger_sql/handle_follow.sql @@ -36,12 +36,13 @@ begin (new.followee_user_id, 'FOLLOWER_COUNT', milestone, new.blocknumber, new.slot, new.created_at) on conflict do nothing; insert into notification - (user_ids, type, specifier, blocknumber, timestamp, data) + (user_ids, type, group_id, specifier, blocknumber, timestamp, data) values ( ARRAY [new.followee_user_id], 'milestone_follower_count', 'milestone:FOLLOWER_COUNT:id:' || new.followee_user_id || ':threshold:' || milestone, + new.followee_user_id, new.blocknumber, new.created_at, json_build_object('type', 'FOLLOWER_COUNT', 'user_id', new.followee_user_id, 'threshold', milestone) @@ -53,13 +54,14 @@ begin -- create a notification for the followee if new.is_delete is false then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [new.followee_user_id], new.created_at, 'follow', + new.follower_user_id, 'follow:' || new.followee_user_id, json_build_object('followee_user_id', new.followee_user_id, 'follower_user_id', new.follower_user_id) ) diff --git a/discovery-provider/alembic/trigger_sql/handle_play.sql b/discovery-provider/alembic/trigger_sql/handle_play.sql index a47a47ebe2e..4e490c93179 100644 --- a/discovery-provider/alembic/trigger_sql/handle_play.sql +++ b/discovery-provider/alembic/trigger_sql/handle_play.sql @@ -23,18 +23,21 @@ begin (new.play_item_id, 'LISTEN_COUNT', milestone, new.slot, new.created_at) on conflict do nothing; select tracks.owner_id into owner_user_id from tracks where is_current and track_id = new.play_item_id; - insert into notification - (user_ids, specifier, type, slot, timestamp, data) - values - ( - array[owner_user_id], - 'milestone:LISTEN_COUNT:id:' || new.play_item_id || ':threshold:' || milestone, - 'milestone', - new.slot, - new.created_at, - json_build_object('type', 'LISTEN_COUNT', 'track_id', new.play_item_id, 'threshold', milestone) - ) - on conflict do nothing; + if owner_user_id is not null then + insert into notification + (user_ids, specifier, group_id, type, slot, timestamp, data) + values + ( + array[owner_user_id], + owner_user_id, + 'milestone:LISTEN_COUNT:id:' || new.play_item_id || ':threshold:' || milestone, + 'milestone', + new.slot, + new.created_at, + json_build_object('type', 'LISTEN_COUNT', 'track_id', new.play_item_id, 'threshold', milestone) + ) + on conflict do nothing; + end if; end if; return null; end; diff --git a/discovery-provider/alembic/trigger_sql/handle_playlist.sql b/discovery-provider/alembic/trigger_sql/handle_playlist.sql index 6150672ceb9..52eaa7bb8a3 100644 --- a/discovery-provider/alembic/trigger_sql/handle_playlist.sql +++ b/discovery-provider/alembic/trigger_sql/handle_playlist.sql @@ -34,26 +34,29 @@ begin end if; begin - for track_item IN select jsonb_array_elements from jsonb_array_elements(new.playlist_contents -> 'track_ids') - loop - if (track_item->>'time')::double precision::int >= extract(epoch from new.updated_at)::int then - select owner_id into track_owner_id from tracks where is_current and track_id=(track_item->>'track')::int; - if track_owner_id != new.playlist_owner_id then - insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) - values - ( - new.blocknumber, - ARRAY [track_owner_id], - new.updated_at, - 'track_added_to_playlist', - 'track_added_to_playlist:playlist_id:' || new.playlist_id || ':track_id:' || (track_item->>'track')::int || ':blocknumber:' || new.blocknumber, - json_build_object('track_id', (track_item->>'track')::int, 'playlist_id', new.playlist_id) - ) - on conflict do nothing; + if new.is_delete IS FALSE and new.is_private IS FALSE then + for track_item IN select jsonb_array_elements from jsonb_array_elements(new.playlist_contents -> 'track_ids') + loop + if (track_item->>'time')::double precision::int >= extract(epoch from new.updated_at)::int then + select owner_id into track_owner_id from tracks where is_current and track_id=(track_item->>'track')::int; + if track_owner_id != new.playlist_owner_id then + insert into notification + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) + values + ( + new.blocknumber, + ARRAY [track_owner_id], + new.updated_at, + 'track_added_to_playlist', + track_owner_id, + 'track_added_to_playlist:playlist_id:' || new.playlist_id || ':track_id:' || (track_item->>'track')::int || ':blocknumber:' || new.blocknumber, + json_build_object('track_id', (track_item->>'track')::int, 'playlist_id', new.playlist_id) + ) + on conflict do nothing; + end if; end if; - end if; - end loop; + end loop; + end if; exception when others then null; end; diff --git a/discovery-provider/alembic/trigger_sql/handle_reaction.sql b/discovery-provider/alembic/trigger_sql/handle_reaction.sql index b8f18e8c5ca..6ba3b9c62b2 100644 --- a/discovery-provider/alembic/trigger_sql/handle_reaction.sql +++ b/discovery-provider/alembic/trigger_sql/handle_reaction.sql @@ -7,14 +7,15 @@ begin if sender_user_id is not null then insert into notification - (slot, user_ids, timestamp, type, specifier, data) + (slot, user_ids, timestamp, type, specifier, group_id, data) values ( new.slot, ARRAY [sender_user_id], new.timestamp, 'reaction', - 'reaction:' || 'reaction_to:' || new.reacted_to || ':slot:' || new.slot, + sender_user_id, + 'reaction:' || 'reaction_to:' || new.reacted_to || ':reaction_type:' || new.reaction_type || ':reaction_value:' || new.reaction_value || ':timestamp:' || new.timestamp, json_build_object('sender_wallet', new.sender_wallet, 'reaction_type', new.reaction_type, 'reacted_to', new.reacted_to, 'reaction_value', new.reaction_value) ) on conflict do nothing; diff --git a/discovery-provider/alembic/trigger_sql/handle_repost.sql b/discovery-provider/alembic/trigger_sql/handle_repost.sql index 9bafb116b97..41970998444 100644 --- a/discovery-provider/alembic/trigger_sql/handle_repost.sql +++ b/discovery-provider/alembic/trigger_sql/handle_repost.sql @@ -73,11 +73,12 @@ begin (new.repost_item_id, milestone_name, milestone, new.blocknumber, new.slot, new.created_at) on conflict do nothing; insert into notification - (user_ids, type, specifier, blocknumber, timestamp, data) + (user_ids, type, specifier, group_id, blocknumber, timestamp, data) values ( ARRAY [owner_user_id], 'milestone', + owner_user_id, 'milestone:' || milestone_name || ':id:' || new.repost_item_id || ':threshold:' || milestone, new.blocknumber, new.created_at, @@ -90,13 +91,14 @@ begin -- create a notification for the reposted content's owner if new.is_delete is false then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [owner_user_id], new.created_at, 'repost', + new.user_id, 'repost:' || new.repost_item_id || ':type:'|| new.repost_type, json_build_object('repost_item_id', new.repost_item_id, 'user_id', new.user_id, 'type', new.repost_type) ) @@ -111,13 +113,14 @@ begin where is_current and track_id = (track_remix_of->'tracks'->0->>'parent_track_id')::int; if is_remix_cosign then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [owner_user_id], new.created_at, 'cosign', + new.user_id, 'cosign:parent_track' || (track_remix_of->'tracks'->0->>'parent_track_id')::int || ':original_track:'|| new.repost_item_id, json_build_object('parent_track_id', (track_remix_of->'tracks'->0->>'parent_track_id')::int, 'track_id', new.repost_item_id, 'track_owner_id', owner_user_id) ) diff --git a/discovery-provider/alembic/trigger_sql/handle_save.sql b/discovery-provider/alembic/trigger_sql/handle_save.sql index 4c8129fc74e..4d38d743b2e 100644 --- a/discovery-provider/alembic/trigger_sql/handle_save.sql +++ b/discovery-provider/alembic/trigger_sql/handle_save.sql @@ -77,11 +77,12 @@ begin (new.save_item_id, milestone_name, milestone, new.blocknumber, new.slot, new.created_at) on conflict do nothing; insert into notification - (user_ids, type, specifier, blocknumber, timestamp, data) + (user_ids, type, specifier, group_id, blocknumber, timestamp, data) values ( ARRAY [owner_user_id], 'milestone', + owner_user_id, 'milestone:' || milestone_name || ':id:' || new.save_item_id || ':threshold:' || milestone, new.blocknumber, new.created_at, @@ -94,13 +95,14 @@ begin -- create a notification for the saved content's owner if new.is_delete is false then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [owner_user_id], new.created_at, 'save', + new.user_id, 'save:' || new.save_item_id || ':type:'|| new.save_type, json_build_object('save_item_id', new.save_item_id, 'user_id', new.user_id, 'type', new.save_type) ) @@ -115,13 +117,14 @@ begin where is_current and track_id = (track_remix_of->'tracks'->0->>'parent_track_id')::int; if is_remix_cosign then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [owner_user_id], new.created_at, 'cosign', + new.user_id, 'cosign:parent_track' || (track_remix_of->'tracks'->0->>'parent_track_id')::int || ':original_track:'|| new.save_item_id, json_build_object('parent_track_id', (track_remix_of->'tracks'->0->>'parent_track_id')::int, 'track_id', new.save_item_id, 'track_owner_id', owner_user_id) ) diff --git a/discovery-provider/alembic/trigger_sql/handle_supporter_rank_ups.sql b/discovery-provider/alembic/trigger_sql/handle_supporter_rank_ups.sql index e9b8d99886d..05029dfeccb 100644 --- a/discovery-provider/alembic/trigger_sql/handle_supporter_rank_ups.sql +++ b/discovery-provider/alembic/trigger_sql/handle_supporter_rank_ups.sql @@ -7,14 +7,15 @@ begin if user_bank_tx is not null then -- create a notification for the sender and receiver insert into notification - (slot, user_ids, timestamp, type, specifier, data) + (slot, user_ids, timestamp, type, specifier, group_id, data) values ( new.slot, ARRAY [new.sender_user_id], user_bank_tx.created_at, 'supporter_rank_up', - 'supporter_rank_up:' || new.rank || new.slot, + new.sender_user_id, + 'supporter_rank_up:' || new.rank || ':slot:' || new.slot, json_build_object('sender_user_id', new.sender_user_id, 'receiver_user_id', new.receiver_user_id, 'rank', new.rank) ), ( @@ -22,7 +23,8 @@ begin ARRAY [new.receiver_user_id], user_bank_tx.created_at, 'supporting_rank_up', - 'supporting_rank_up:' || new.rank || new.slot, + new.receiver_user_id, + 'supporting_rank_up:' || new.rank || ':slot:' || new.slot, json_build_object('sender_user_id', new.sender_user_id, 'receiver_user_id', new.receiver_user_id, 'rank', new.rank) ) on conflict do nothing; diff --git a/discovery-provider/alembic/trigger_sql/handle_track.sql b/discovery-provider/alembic/trigger_sql/handle_track.sql index 776c6ba46de..3a9a4833b02 100644 --- a/discovery-provider/alembic/trigger_sql/handle_track.sql +++ b/discovery-provider/alembic/trigger_sql/handle_track.sql @@ -23,18 +23,19 @@ begin -- If remix, create notification begin - if new.remix_of is not null then + if new.remix_of is not null AND new.is_unlisted = FALSE AND new.is_delete = FALSE AND new.stem_of IS NULL then select owner_id into parent_track_owner_id from tracks where is_current and track_id = (new.remix_of->'tracks'->0->>'parent_track_id')::int limit 1; if parent_track_owner_id is not null then insert into notification - (blocknumber, user_ids, timestamp, type, specifier, data) + (blocknumber, user_ids, timestamp, type, specifier, group_id, data) values ( new.blocknumber, ARRAY [parent_track_owner_id], new.updated_at, 'remix', - 'remix:' || new.track_id || ':parent_track:' || (new.remix_of->'tracks'->0->>'parent_track_id')::int || ':blocknumber:' || new.blocknumber, + new.owner_id, + 'remix:track:' || new.track_id || ':parent_track:' || (new.remix_of->'tracks'->0->>'parent_track_id')::int || ':blocknumber:' || new.blocknumber, json_build_object('track_id', new.track_id, 'parent_track_id', (new.remix_of->'tracks'->0->>'parent_track_id')::int) ) on conflict do nothing; diff --git a/discovery-provider/alembic/trigger_sql/handle_user_tip.sql b/discovery-provider/alembic/trigger_sql/handle_user_tip.sql index 180b1b672ed..fcd9516d36c 100644 --- a/discovery-provider/alembic/trigger_sql/handle_user_tip.sql +++ b/discovery-provider/alembic/trigger_sql/handle_user_tip.sql @@ -3,14 +3,15 @@ begin -- create a notification for the sender and receiver insert into notification - (slot, user_ids, timestamp, type, specifier, data) + (slot, user_ids, timestamp, type, specifier, group_id, data) values ( new.slot, ARRAY [new.receiver_user_id], new.created_at, 'tip_receive', - 'tip_receive:' || new.receiver_user_id || ':' || new.slot, + new.receiver_user_id, + 'tip_receive:user_id:' || new.receiver_user_id || ':slot:' || new.slot, json_build_object('sender_user_id', new.sender_user_id, 'receiver_user_id', new.receiver_user_id, 'amount', new.amount) ), ( @@ -18,7 +19,8 @@ begin ARRAY [new.sender_user_id], new.created_at, 'tip_send', - 'tip_send:' || new.sender_user_id || ':' || new.slot, + new.sender_user_id, + 'tip_send:user_id:' || new.sender_user_id || ':slot:' || new.slot, json_build_object('sender_user_id', new.sender_user_id, 'receiver_user_id', new.receiver_user_id, 'amount', new.amount) ) on conflict do nothing; diff --git a/discovery-provider/alembic/versions/9931f7fd118f_migrate_miletones_to_notifications.py b/discovery-provider/alembic/versions/9931f7fd118f_migrate_miletones_to_notifications.py index ef0bd66a374..96c0059a278 100644 --- a/discovery-provider/alembic/versions/9931f7fd118f_migrate_miletones_to_notifications.py +++ b/discovery-provider/alembic/versions/9931f7fd118f_migrate_miletones_to_notifications.py @@ -27,11 +27,12 @@ def upgrade(): begin; insert into notification - (user_ids, specifier, type, blocknumber, timestamp, data) + (user_ids, specifier, group_id, type, blocknumber, timestamp, data) select array[tracks.owner_id], + tracks.owner_id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_track_repost_count', + 'milestone', milestones.blocknumber, milestones.timestamp, ('{"type":"'|| milestones.name || '", "track_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -41,11 +42,12 @@ def upgrade(): insert into notification - (user_ids, specifier, type, blocknumber, timestamp, data) + (user_ids, specifier, group_id, type, blocknumber, timestamp, data) select array[playlists.playlist_owner_id], + playlists.playlist_owner_id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_playlist_repost_count', + 'milestone', milestones.blocknumber, milestones.timestamp, ('{"type":"'|| milestones.name || '", "playlist_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -54,11 +56,12 @@ def upgrade(): where playlists.is_current AND milestones.blocknumber !=0 and milestones.name='PLAYLIST_REPOST_COUNT'; insert into notification - (user_ids, specifier, type, blocknumber, timestamp, data) + (user_ids, specifier, group_id, type, blocknumber, timestamp, data) select array[tracks.owner_id], + tracks.owner_id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_track_save_count', + 'milestone', milestones.blocknumber, milestones.timestamp, ('{"type":"'|| milestones.name || '", "track_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -67,11 +70,12 @@ def upgrade(): where tracks.is_current AND milestones.blocknumber !=0 and milestones.name='TRACK_SAVE_COUNT'; insert into notification - (user_ids, specifier, type, blocknumber, timestamp, data) + (user_ids, specifier, group_id, type, blocknumber, timestamp, data) select array[playlists.playlist_owner_id], + playlists.playlist_owner_id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_playlist_save_count', + 'milestone', milestones.blocknumber, milestones.timestamp, ('{"type":"'|| milestones.name || '", "playlist_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -80,11 +84,12 @@ def upgrade(): where playlists.is_current AND milestones.blocknumber !=0 and milestones.name='PLAYLIST_SAVE_COUNT'; insert into notification - (user_ids, specifier, type, blocknumber, timestamp, data) + (user_ids, specifier, group_id, type, blocknumber, timestamp, data) select array[milestones.id], + milestones.id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_follower_count', + 'milestone', milestones.blocknumber, milestones.timestamp, ('{"type":"'|| milestones.name || '", "user_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -92,11 +97,12 @@ def upgrade(): where milestones.name='FOLLOWER_COUNT' AND milestones.blocknumber !=0; insert into notification - (user_ids, specifier, type, slot, timestamp, data) + (user_ids, specifier, group_id, type, slot, timestamp, data) select array[tracks.owner_id], + tracks.owner_id, 'milestone:' || milestones.name || ':id:' || milestones.id || ':threshold:' || milestones.threshold, - 'milestone_listen_count', + 'milestone', milestones.slot, milestones.timestamp, ('{"type":"'|| milestones.name || '", "track_id":' || milestones.id || ', "threshold":' || milestones.threshold || '}')::json @@ -118,12 +124,7 @@ def downgrade(): """ delete from notification where type in ( - 'milestone_listen_count', - 'milestone_track_save_count', - 'milestone_track_repost_count', - 'milestone_playlist_save_count', - 'milestone_playlist_repost_count', - 'milestone_follower_count' + 'milestone', ); """ ) diff --git a/discovery-provider/alembic/versions/b0623220e904_create_notifications_table.py b/discovery-provider/alembic/versions/b0623220e904_create_notifications_table.py index 2b587fd7677..9b0822bd220 100644 --- a/discovery-provider/alembic/versions/b0623220e904_create_notifications_table.py +++ b/discovery-provider/alembic/versions/b0623220e904_create_notifications_table.py @@ -5,10 +5,14 @@ Create Date: 2022-06-27 17:04:24.686274 """ +import logging + import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql +logger = logging.getLogger(__name__) + # revision identifiers, used by Alembic. revision = "b0623220e904" down_revision = "d0dfb103535b" @@ -23,6 +27,13 @@ def foreign_key_exists(table_name, foreign_key): return any(fk["name"] == foreign_key for fk in foreign_keys) +def unique_constraints_exists(table_name, unique_contraint): + bind = op.get_context().bind + insp = sa.inspect(bind) + unique_constraints = insp.get_unique_constraints(table_name) + return any(uc["name"] == unique_contraint for uc in unique_constraints) + + def upgrade(): op.create_table( "notification_group", @@ -43,6 +54,7 @@ def upgrade(): "id", sa.Integer(), primary_key=True, nullable=False, autoincrement=True ), sa.Column("specifier", sa.String(), nullable=False), + sa.Column("group_id", sa.String(), nullable=False), sa.Column("notification_group_id", sa.Integer(), nullable=True), sa.Column("type", sa.String(), nullable=False), sa.Column("slot", sa.Integer(), nullable=True), @@ -68,6 +80,14 @@ def upgrade(): info={"if_not_exists": True}, ) + if not unique_constraints_exists("notification", "uq_notification"): + op.create_unique_constraint( + "uq_notification", + "notification", + ["group_id", "specifier"], + info={"if_not_exists": True}, + ) + op.create_index( op.f("ix_notification"), "notification", @@ -98,6 +118,12 @@ def downgrade(): "foreignkey", info={"if_exists": True}, ) + op.drop_constraint( + "uq_notification", + "notification", + info={"if_exists": True}, + ) + op.drop_table("notification", info={"if_exists": True}) op.drop_table("notification_group", info={"if_exists": True}) # ### end Alembic commands ### diff --git a/discovery-provider/integration_tests/notifications/test_challenge_disbursement.py b/discovery-provider/integration_tests/notifications/test_challenge_disbursement.py index 56725462a69..ee975d99c36 100644 --- a/discovery-provider/integration_tests/notifications/test_challenge_disbursement.py +++ b/discovery-provider/integration_tests/notifications/test_challenge_disbursement.py @@ -38,7 +38,8 @@ def test_challenge_disbursement_notification(app): ) assert len(notifications) == 3 - assert notifications[0].specifier == "challenge_reward:1:2" + assert notifications[0].specifier == "1" + assert notifications[0].group_id == "challenge_reward:1:challenge:2:specifier:2" assert notifications[0].notification_group_id == None assert notifications[0].type == "challenge_reward" assert notifications[0].slot == 2 diff --git a/discovery-provider/integration_tests/notifications/test_follow.py b/discovery-provider/integration_tests/notifications/test_follow.py index 2f4328ef621..48e7d47bb44 100644 --- a/discovery-provider/integration_tests/notifications/test_follow.py +++ b/discovery-provider/integration_tests/notifications/test_follow.py @@ -2,7 +2,7 @@ from typing import List from integration_tests.utils import populate_mock_db -from sqlalchemy import asc +from sqlalchemy import asc, desc from src.models.notifications.notification import Notification from src.utils.db_session import get_db @@ -17,26 +17,57 @@ def test_repost_notification(app): # Insert a follow and check that a notificaiton is created for the followee entities = { - "users": [{"user_id": i + 1} for i in range(5)], + "users": [{"user_id": i + 1} for i in range(50)], "follows": [ {"follower_user_id": i + 2, "followee_user_id": i + 1} for i in range(4) - ], + ] + + [{"follower_user_id": i + 4, "followee_user_id": 1} for i in range(25)], } + populate_mock_db(db, entities) with db.scoped_session() as session: notifications: List[Notification] = ( - session.query(Notification).order_by(asc(Notification.blocknumber)).all() + session.query(Notification) + .filter(Notification.type == "follow") + .order_by(asc(Notification.blocknumber)) + .all() ) - assert len(notifications) == 4 - assert notifications[0].specifier == "follow:1" - assert notifications[1].specifier == "follow:2" - assert notifications[2].specifier == "follow:3" - assert notifications[3].specifier == "follow:4" + assert len(notifications) == 29 + assert notifications[0].group_id == "follow:1" + assert notifications[0].specifier == "2" + assert notifications[1].group_id == "follow:2" + assert notifications[1].specifier == "3" + assert notifications[2].group_id == "follow:3" + assert notifications[2].specifier == "4" + assert notifications[3].group_id == "follow:4" + assert notifications[3].specifier == "5" assert notifications[0].notification_group_id == None assert notifications[0].type == "follow" assert notifications[0].slot == None assert notifications[0].blocknumber == 0 assert notifications[0].data == {"followee_user_id": 1, "follower_user_id": 2} assert notifications[0].user_ids == [1] + + milstone_notifications: List[Notification] = ( + session.query(Notification) + .filter(Notification.type == "milestone_follower_count") + .order_by(desc(Notification.group_id)) + .all() + ) + + assert len(milstone_notifications) == 2 + assert ( + milstone_notifications[0].group_id + == "milestone:FOLLOWER_COUNT:id:1:threshold:25" + ) + assert milstone_notifications[0].specifier == "1" + assert milstone_notifications[0].notification_group_id == None + assert milstone_notifications[0].type == "milestone_follower_count" + assert milstone_notifications[0].data == { + "type": "FOLLOWER_COUNT", + "user_id": 1, + "threshold": 25, + } + assert milstone_notifications[0].user_ids == [1] diff --git a/discovery-provider/integration_tests/notifications/test_play_milestone.py b/discovery-provider/integration_tests/notifications/test_play_milestone.py index 41d6aaaeace..70c724a7496 100644 --- a/discovery-provider/integration_tests/notifications/test_play_milestone.py +++ b/discovery-provider/integration_tests/notifications/test_play_milestone.py @@ -30,9 +30,8 @@ def test_play_milsetone_notification(app): session.query(Notification).order_by(desc(Notification.slot)).all() ) assert len(notifications) == 3 - assert ( - notifications[0].specifier == "milestone:LISTEN_COUNT:id:100:threshold:50" - ) + assert notifications[0].specifier == "2" + assert notifications[0].group_id == "milestone:LISTEN_COUNT:id:100:threshold:50" assert notifications[0].notification_group_id == None assert notifications[0].type == "milestone" assert notifications[0].slot == 50 @@ -44,9 +43,7 @@ def test_play_milsetone_notification(app): } assert notifications[0].user_ids == [2] - assert ( - notifications[1].specifier == "milestone:LISTEN_COUNT:id:100:threshold:25" - ) - assert ( - notifications[2].specifier == "milestone:LISTEN_COUNT:id:100:threshold:10" - ) + assert notifications[1].specifier == "2" + assert notifications[1].group_id == "milestone:LISTEN_COUNT:id:100:threshold:25" + assert notifications[2].specifier == "2" + assert notifications[2].group_id == "milestone:LISTEN_COUNT:id:100:threshold:10" diff --git a/discovery-provider/integration_tests/notifications/test_playlist.py b/discovery-provider/integration_tests/notifications/test_playlist.py index 9a3d0f08cc5..69f946215e6 100644 --- a/discovery-provider/integration_tests/notifications/test_playlist.py +++ b/discovery-provider/integration_tests/notifications/test_playlist.py @@ -53,9 +53,10 @@ def test_playlist_track_added_notification(app): ) assert len(notifications) == 2 assert ( - notifications[0].specifier + notifications[0].group_id == "track_added_to_playlist:playlist_id:0:track_id:20:blocknumber:0" ) + assert notifications[0].specifier == "1" assert notifications[0].notification_group_id == None assert notifications[0].type == "track_added_to_playlist" assert notifications[0].slot == None @@ -64,9 +65,10 @@ def test_playlist_track_added_notification(app): assert notifications[0].user_ids == [1] assert ( - notifications[1].specifier + notifications[1].group_id == "track_added_to_playlist:playlist_id:0:track_id:30:blocknumber:0" ) + assert notifications[1].specifier == "15" assert notifications[1].notification_group_id == None assert notifications[1].type == "track_added_to_playlist" assert notifications[1].slot == None diff --git a/discovery-provider/integration_tests/notifications/test_reaction.py b/discovery-provider/integration_tests/notifications/test_reaction.py index f3152763b54..10e5f9d2d6d 100644 --- a/discovery-provider/integration_tests/notifications/test_reaction.py +++ b/discovery-provider/integration_tests/notifications/test_reaction.py @@ -1,4 +1,5 @@ import logging +from datetime import datetime from typing import List from integration_tests.utils import populate_mock_db @@ -10,10 +11,11 @@ # ========================================== Start Tests ========================================== -def test_repost_notification(app): +def test_reaction_notification(app): with app.app_context(): db = get_db() + now = datetime.now() # Insert a reaction and check that a notificaiton is created entities = { "users": [{"user_id": i + 1, "wallet": "0x" + str(i)} for i in range(3)], @@ -25,6 +27,7 @@ def test_repost_notification(app): "id": i + 1, "sender_wallet": "0x" + str(i), "reacted_to": "react_" + str(i), + "timestamp": now, } for i in range(3) ], @@ -37,9 +40,24 @@ def test_repost_notification(app): session.query(Notification).order_by(asc(Notification.slot)).all() ) assert len(notifications) == 3 - assert notifications[0].specifier == "reaction:reaction_to:react_0:slot:0" - assert notifications[1].specifier == "reaction:reaction_to:react_1:slot:1" - assert notifications[2].specifier == "reaction:reaction_to:react_2:slot:2" + assert notifications[0].specifier == "1" + assert notifications[ + 0 + ].group_id == "reaction:reaction_to:react_0:reaction_type:type:reaction_value:1:timestamp:" + str( + now + ) + assert notifications[1].specifier == "2" + assert notifications[ + 1 + ].group_id == "reaction:reaction_to:react_1:reaction_type:type:reaction_value:1:timestamp:" + str( + now + ) + assert notifications[2].specifier == "3" + assert notifications[ + 2 + ].group_id == "reaction:reaction_to:react_2:reaction_type:type:reaction_value:1:timestamp:" + str( + now + ) assert notifications[0].notification_group_id == None assert notifications[0].type == "reaction" assert notifications[0].slot == 0 diff --git a/discovery-provider/integration_tests/notifications/test_repost.py b/discovery-provider/integration_tests/notifications/test_repost.py index f5ef23c5a9c..f937f2ab193 100644 --- a/discovery-provider/integration_tests/notifications/test_repost.py +++ b/discovery-provider/integration_tests/notifications/test_repost.py @@ -31,13 +31,15 @@ def test_repost_notification(app): ], } populate_mock_db(db, entities) + populate_mock_db(db, entities) with db.scoped_session() as session: notifications: List[Notification] = session.query(Notification).all() assert len(notifications) == 1 notification = notifications[0] - assert notification.specifier == "repost:100:type:track" + assert notification.specifier == "1" + assert notification.group_id == "repost:100:type:track" assert notification.notification_group_id == None assert notification.type == "repost" assert notification.slot == None diff --git a/discovery-provider/integration_tests/notifications/test_save.py b/discovery-provider/integration_tests/notifications/test_save.py index 64771e89bc3..7baaf9d4ada 100644 --- a/discovery-provider/integration_tests/notifications/test_save.py +++ b/discovery-provider/integration_tests/notifications/test_save.py @@ -36,7 +36,8 @@ def test_save_notification(app): notifications: List[Notification] = session.query(Notification).all() assert len(notifications) == 1 notification = notifications[0] - assert notification.specifier == "save:100:type:track" + assert notification.specifier == "1" + assert notification.group_id == "save:100:type:track" assert notification.notification_group_id == None assert notification.type == "save" assert notification.slot == None diff --git a/discovery-provider/integration_tests/notifications/test_supporter_rank_ups.py b/discovery-provider/integration_tests/notifications/test_supporter_rank_ups.py index 7c76cbe0ca5..a09ac208732 100644 --- a/discovery-provider/integration_tests/notifications/test_supporter_rank_ups.py +++ b/discovery-provider/integration_tests/notifications/test_supporter_rank_ups.py @@ -51,7 +51,8 @@ def test_supporter_rank_up_notification(app): assert len(supporter_notifications) == 3 assert len(supporting_notifications) == 3 - assert supporter_notifications[0].specifier == "supporter_rank_up:32" + assert supporter_notifications[0].specifier == "1" + assert supporter_notifications[0].group_id == "supporter_rank_up:3:slot:2" assert supporter_notifications[0].notification_group_id == None assert supporter_notifications[0].type == "supporter_rank_up" assert supporter_notifications[0].slot == 2 @@ -63,7 +64,8 @@ def test_supporter_rank_up_notification(app): } assert supporter_notifications[0].user_ids == [1] - assert supporting_notifications[0].specifier == "supporting_rank_up:32" + assert supporting_notifications[0].specifier == "3" + assert supporting_notifications[0].group_id == "supporting_rank_up:3:slot:2" assert supporting_notifications[0].notification_group_id == None assert supporting_notifications[0].type == "supporting_rank_up" assert supporting_notifications[0].slot == 2 diff --git a/discovery-provider/integration_tests/notifications/test_track.py b/discovery-provider/integration_tests/notifications/test_track.py index 533a0e73acc..b7401542cc3 100644 --- a/discovery-provider/integration_tests/notifications/test_track.py +++ b/discovery-provider/integration_tests/notifications/test_track.py @@ -34,7 +34,8 @@ def test_track_remix_notification(app): notifications: List[Notification] = session.query(Notification).all() assert len(notifications) == 1 notification = notifications[0] - assert notification.specifier == "remix:100:parent_track:20:blocknumber:1" + assert notification.specifier == "2" + assert notification.group_id == "remix:track:100:parent_track:20:blocknumber:1" assert notification.notification_group_id == None assert notification.type == "remix" assert notification.slot == None diff --git a/discovery-provider/integration_tests/notifications/test_user_tip.py b/discovery-provider/integration_tests/notifications/test_user_tip.py index e80ed495a78..cdf2619114b 100644 --- a/discovery-provider/integration_tests/notifications/test_user_tip.py +++ b/discovery-provider/integration_tests/notifications/test_user_tip.py @@ -43,7 +43,8 @@ def test_supporter_rank_up_notification(app): assert len(send_notifications) == 3 assert len(receive_notifications) == 3 - assert send_notifications[0].specifier == "tip_send:1:2" + assert send_notifications[0].specifier == "1" + assert send_notifications[0].group_id == "tip_send:user_id:1:slot:2" assert send_notifications[0].notification_group_id == None assert send_notifications[0].type == "tip_send" assert send_notifications[0].slot == 2 @@ -55,7 +56,8 @@ def test_supporter_rank_up_notification(app): } assert send_notifications[0].user_ids == [1] - assert receive_notifications[0].specifier == "tip_receive:3:2" + assert receive_notifications[0].specifier == "3" + assert receive_notifications[0].group_id == "tip_receive:user_id:3:slot:2" assert receive_notifications[0].notification_group_id == None assert receive_notifications[0].type == "tip_receive" assert receive_notifications[0].slot == 2 diff --git a/discovery-provider/src/models/notifications/notification.py b/discovery-provider/src/models/notifications/notification.py index 0ff5562f18a..603e1450c23 100644 --- a/discovery-provider/src/models/notifications/notification.py +++ b/discovery-provider/src/models/notifications/notification.py @@ -1,4 +1,13 @@ -from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, text +from sqlalchemy import ( + Column, + DateTime, + ForeignKey, + Index, + Integer, + String, + UniqueConstraint, + text, +) from sqlalchemy.dialects import postgresql from sqlalchemy.orm import relationship from src.models.base import Base @@ -14,6 +23,7 @@ class Notification(Base, RepresentableMixin): server_default=text("nextval('notification_id_seq'::regclass)"), ) specifier = Column(String, nullable=False) + group_id = Column(String, nullable=False) notification_group_id = Column(Integer, ForeignKey("notification_group.id")) # type: ignore type = Column(String, nullable=False) slot = Column(Integer) @@ -21,6 +31,7 @@ class Notification(Base, RepresentableMixin): timestamp = Column(DateTime, nullable=False) data = Column(postgresql.JSONB()) # type: ignore user_ids = Column(postgresql.ARRAY(Integer()), index=True) + UniqueConstraint("group_id", "specifier", name="uq_notification") class NotificationGroup(Base, RepresentableMixin): diff --git a/discovery-provider/src/tasks/index_rewards_manager.py b/discovery-provider/src/tasks/index_rewards_manager.py index 185aff1ce8d..d9d65f48281 100644 --- a/discovery-provider/src/tasks/index_rewards_manager.py +++ b/discovery-provider/src/tasks/index_rewards_manager.py @@ -268,6 +268,7 @@ def process_batch_sol_reward_manager_txs( created_at=datetime.datetime.utcfromtimestamp(tx["timestamp"]), ) ) + session.flush() # No instruction found if tx["transfer_instruction"] is None: logger.warning(