From e78bc3b980a1574a8c8157ac986b994df49c79f3 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Tue, 12 May 2026 15:17:23 -0700 Subject: [PATCH] chore(discovery-provider): remove dead query helpers + their tests (Option B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #14298. That PR removed query files with literally zero callers in the repo. This one tackles the deferred set: query helpers whose only callers are tests that exist to verify them. The production code paths are unreached after the Flask removal in #14236; the tests were testing dead code (or, in some cases, were using a dead query as a verification tool for a still-live indexer task — see the risk section below). ## Scope 82 files / ~14k lines deleted. ### Production query helpers (31) src/queries/get_aggregate_route_metrics.py src/queries/get_app_name_metrics.py src/queries/get_attestation.py src/queries/get_challenges.py src/queries/get_collection_library.py src/queries/get_follow_intersection_users.py src/queries/get_genre_metrics.py src/queries/get_historical_route_metrics.py src/queries/get_notifications.py src/queries/get_personal_route_metrics.py src/queries/get_plays_metrics.py src/queries/get_prev_track_entries.py src/queries/get_related_artists.py src/queries/get_remixable_tracks.py src/queries/get_remixes_of.py src/queries/get_repost_feed_for_user.py src/queries/get_tips.py src/queries/get_top_genre_users.py src/queries/get_top_listeners_for_track.py src/queries/get_top_user_track_tags.py src/queries/get_top_users.py src/queries/get_total_plays.py src/queries/get_trailing_metrics.py src/queries/get_undisbursed_challenges.py src/queries/get_usdc_purchases.py src/queries/get_usdc_transactions_history.py src/queries/get_user_listen_counts_monthly.py src/queries/get_user_listening_history.py src/queries/get_user_playlist_update.py src/queries/get_user_signals.py src/queries/get_users_account.py ### Unit tests in src/queries/ (2) src/queries/get_genre_metrics_unit_test.py src/queries/get_plays_metrics_unit_test.py ### Integration tests (49) integration_tests/challenges/test_challenges.py integration_tests/queries/test_get_aggregate_app_metrics.py integration_tests/queries/test_get_aggregate_route_metrics.py integration_tests/queries/test_get_aggregate_route_metrics_trailing_month.py integration_tests/queries/test_get_attestation.py integration_tests/queries/test_get_challenges.py integration_tests/queries/test_get_collections_library.py integration_tests/queries/test_get_genre_metrics.py integration_tests/queries/test_get_historical_app_metrics.py integration_tests/queries/test_get_historical_route_metrics.py integration_tests/queries/test_get_personal_route_metrics.py integration_tests/queries/test_get_plays_metrics.py integration_tests/queries/test_get_prev_track_entries.py integration_tests/queries/test_get_related_artists.py integration_tests/queries/test_get_repost_feed_for_user.py integration_tests/queries/test_get_tips.py integration_tests/queries/test_get_top_genre_users.py integration_tests/queries/test_get_top_user_track_tags.py integration_tests/queries/test_get_total_plays.py integration_tests/queries/test_get_tracks.py integration_tests/queries/test_get_usdc_purchases.py integration_tests/queries/test_get_usdc_transactions_history.py integration_tests/queries/test_get_user_listen_counts_monthly.py integration_tests/queries/test_get_user_listening_history.py integration_tests/queries/test_get_user_signals.py integration_tests/queries/test_get_users_account.py integration_tests/queries/test_mutual_follows.py integration_tests/queries/test_notifications/test_announcement_notification.py integration_tests/queries/test_notifications/test_follow_notification.py integration_tests/queries/test_notifications/test_get_unread_notification_count.py integration_tests/queries/test_notifications/test_paginate_notification.py integration_tests/queries/test_notifications/test_remix_track_notification.py integration_tests/queries/test_notifications/test_repost_notification.py integration_tests/queries/test_notifications/test_repost_repost_notifications.py integration_tests/queries/test_notifications/test_save_notification.py integration_tests/queries/test_notifications/test_save_of_repost_notification.py integration_tests/queries/test_notifications/test_tastemaker_notification.py integration_tests/queries/test_notifications/test_track_added_to_playlist_notification.py integration_tests/queries/test_notifications/test_trending_playlist_notification.py integration_tests/queries/test_notifications/test_trending_underground_notification.py integration_tests/queries/test_notifications/test_user_tier_change_notification.py integration_tests/queries/test_populate_user_metadata.py integration_tests/queries/test_undisbursed_challenges.py integration_tests/queries/test_user_playlist_update.py integration_tests/tasks/test_artist_remix_contest_ending_soon_notification.py integration_tests/tasks/test_artist_remix_contest_submissions_notification.py integration_tests/tasks/test_fan_remix_contest_ended_notification.py integration_tests/tasks/test_fan_remix_contest_ending_soon_notification.py integration_tests/tasks/test_fan_remix_contest_started_notification.py ## How this set was identified For each `src/queries/*.py`, counted callers under `packages/discovery-provider/` excluding the file itself and excluding test files (`*_unit_test.py`, `/test_*.py`, anything under `integration_tests/`): ```bash for f in packages/discovery-provider/src/queries/*.py; do base=$(basename "$f" .py) live_refs=$(rg -l "\b$base\b" packages/discovery-provider --type py \ | grep -v "^$f$" | grep -v "_unit_test\.py$" \ | grep -v "/test_.*\.py$" | grep -v "/integration_tests/" \ | wc -l) [ "$live_refs" = "0" ] && echo "$base" done ``` Files with 0 live refs but >0 total refs (i.e., only tests reach them) landed in this PR. Each test was paired back to the prod module(s) it imports. Tests that exclusively import from the dead set are included in the deletion; no test is being deleted that imports any surviving production module. ## Risk: tests that incidentally exercise live indexer code A handful of the integration tests use a dead query as their *assertion tool* but exercise still-live indexer / task code in the setup phase. Deleting them loses integration coverage for the underlying live module. Reviewer should explicitly OK each of these or restore the test (and either keep the dead query or refactor the test to query the DB directly): - `integration_tests/challenges/test_challenges.py` — exercises `src.challenges.challenge_event_bus`, `src.challenges.challenge` (live). Asserts via `get_challenges`. - `integration_tests/queries/test_get_challenges.py` — exercises `src.challenges.listen_streak_endless_challenge`, `src.challenges.referral_challenge` (live). Asserts via `get_challenges`. - `integration_tests/queries/test_undisbursed_challenges.py` — uses the challenges/disbursement models (live). Asserts via `get_undisbursed_challenges`. - `integration_tests/queries/test_get_attestation.py` — exercises `src.tasks.index_oracles` (live). Asserts via `get_attestation`. - `integration_tests/queries/test_get_plays_metrics.py` — exercises `src.tasks.index_hourly_play_counts._index_hourly_play_counts` (live). Asserts via `get_plays_metrics`. - `integration_tests/queries/test_get_top_genre_users.py` — exercises `src.tasks.update_aggregates._update_aggregates` (live). Asserts via `get_top_genre_users`. - `integration_tests/queries/test_get_user_listening_history.py` — exercises `src.tasks.user_listening_history.index_user_listening_history` (live). Asserts via `get_user_listening_history` + `get_top_listeners_for_track`. - The 14 `integration_tests/queries/test_notifications/test_*` files + the 5 `integration_tests/tasks/test_*_remix_contest_*` files — each runs a notification-creation task (live, in `src.tasks.remix_contest_notifications.*` etc.), then asserts via `get_notifications`. The other ~25 tests in the deletion set are pure black-box assertions of the dead query helper alone (e.g. `test_get_tips.py`, `test_get_repost_feed_for_user.py`) — no live coverage is lost there. ## Verification - `python3 -m compileall -q packages/discovery-provider/src packages/discovery-provider/integration_tests` → exit 0 - Post-deletion `rg` for every removed module name across `packages/discovery-provider/` found no remaining import references. ## What was intentionally NOT touched - `src/queries/get_extended_purchase_gate.py` — only test caller is `test_get_extended_purchase_gate.py`, but `src/tasks/index_payment_router.py` also imports it. Live. - `src/queries/get_block_confirmation_unit_test.py` and `src/queries/get_latest_play_unit_test.py` and their prod pairs — already in #14298. - All indexing code under `src/tasks/`, `src/eth_indexing/`, `src/solana/`, `src/challenges/`. None flagged as dead. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../challenges/test_challenges.py | 569 --------- .../queries/test_get_aggregate_app_metrics.py | 152 --- .../test_get_aggregate_route_metrics.py | 215 ---- ..._aggregate_route_metrics_trailing_month.py | 51 - .../queries/test_get_attestation.py | 326 ----- .../queries/test_get_challenges.py | 886 -------------- .../queries/test_get_collections_library.py | 323 ----- .../queries/test_get_genre_metrics.py | 99 -- .../test_get_historical_app_metrics.py | 127 -- .../test_get_historical_route_metrics.py | 110 -- .../test_get_personal_route_metrics.py | 223 ---- .../queries/test_get_plays_metrics.py | 158 --- .../queries/test_get_prev_track_entries.py | 132 -- .../queries/test_get_related_artists.py | 167 --- .../queries/test_get_repost_feed_for_user.py | 119 -- .../queries/test_get_tips.py | 81 -- .../queries/test_get_top_genre_users.py | 46 - .../queries/test_get_top_user_track_tags.py | 34 - .../queries/test_get_total_plays.py | 41 - .../queries/test_get_tracks.py | 575 --------- .../queries/test_get_usdc_purchases.py | 233 ---- .../test_get_usdc_transactions_history.py | 146 --- .../test_get_user_listen_counts_monthly.py | 73 -- .../test_get_user_listening_history.py | 428 ------- .../queries/test_get_user_signals.py | 129 -- .../queries/test_get_users_account.py | 118 -- .../queries/test_mutual_follows.py | 43 - .../test_announcement_notification.py | 78 -- .../test_follow_notification.py | 123 -- .../test_get_unread_notification_count.py | 64 - .../test_paginate_notification.py | 65 - .../test_remix_track_notification.py | 89 -- .../test_repost_notification.py | 82 -- .../test_repost_repost_notifications.py | 120 -- .../test_save_notification.py | 70 -- .../test_save_of_repost_notification.py | 118 -- .../test_tastemaker_notification.py | 76 -- ...st_track_added_to_playlist_notification.py | 47 - .../test_trending_playlist_notification.py | 91 -- .../test_trending_underground_notification.py | 88 -- .../test_user_tier_change_notification.py | 45 - .../queries/test_populate_user_metadata.py | 164 --- .../queries/test_undisbursed_challenges.py | 239 ---- .../queries/test_user_playlist_update.py | 103 -- ..._remix_contest_ending_soon_notification.py | 166 --- ..._remix_contest_submissions_notification.py | 131 -- ...st_fan_remix_contest_ended_notification.py | 274 ----- ..._remix_contest_ending_soon_notification.py | 274 ----- ..._fan_remix_contest_started_notification.py | 1068 ----------------- .../queries/get_aggregate_route_metrics.py | 379 ------ .../src/queries/get_app_name_metrics.py | 171 --- .../src/queries/get_attestation.py | 257 ---- .../src/queries/get_challenges.py | 314 ----- .../src/queries/get_collection_library.py | 269 ----- .../queries/get_follow_intersection_users.py | 41 - .../src/queries/get_genre_metrics.py | 49 - .../queries/get_genre_metrics_unit_test.py | 2 - .../queries/get_historical_route_metrics.py | 133 -- .../src/queries/get_notifications.py | 719 ----------- .../src/queries/get_personal_route_metrics.py | 304 ----- .../src/queries/get_plays_metrics.py | 62 - .../queries/get_plays_metrics_unit_test.py | 2 - .../src/queries/get_prev_track_entries.py | 53 - .../src/queries/get_related_artists.py | 108 -- .../src/queries/get_remixable_tracks.py | 90 -- .../src/queries/get_remixes_of.py | 159 --- .../src/queries/get_repost_feed_for_user.py | 178 --- .../src/queries/get_tips.py | 339 ------ .../src/queries/get_top_genre_users.py | 53 - .../queries/get_top_listeners_for_track.py | 69 -- .../src/queries/get_top_user_track_tags.py | 45 - .../src/queries/get_top_users.py | 36 - .../src/queries/get_total_plays.py | 26 - .../src/queries/get_trailing_metrics.py | 60 - .../src/queries/get_undisbursed_challenges.py | 128 -- .../src/queries/get_usdc_purchases.py | 150 --- .../queries/get_usdc_transactions_history.py | 155 --- .../queries/get_user_listen_counts_monthly.py | 54 - .../src/queries/get_user_listening_history.py | 180 --- .../src/queries/get_user_playlist_update.py | 73 -- .../src/queries/get_user_signals.py | 54 - .../src/queries/get_users_account.py | 159 --- 82 files changed, 14050 deletions(-) delete mode 100644 packages/discovery-provider/integration_tests/challenges/test_challenges.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_aggregate_app_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics_trailing_month.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_attestation.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_challenges.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_collections_library.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_genre_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_historical_app_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_historical_route_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_personal_route_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_plays_metrics.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_prev_track_entries.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_related_artists.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_repost_feed_for_user.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_tips.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_top_genre_users.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_top_user_track_tags.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_total_plays.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_tracks.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_usdc_purchases.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_usdc_transactions_history.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_user_listen_counts_monthly.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_user_listening_history.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_user_signals.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_get_users_account.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_mutual_follows.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_announcement_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_follow_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_get_unread_notification_count.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_paginate_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_remix_track_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_repost_notifications.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_save_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_save_of_repost_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_tastemaker_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_playlist_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_playlist_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_underground_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_notifications/test_user_tier_change_notification.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_populate_user_metadata.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_undisbursed_challenges.py delete mode 100644 packages/discovery-provider/integration_tests/queries/test_user_playlist_update.py delete mode 100644 packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ending_soon_notification.py delete mode 100644 packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_submissions_notification.py delete mode 100644 packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ended_notification.py delete mode 100644 packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ending_soon_notification.py delete mode 100644 packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_started_notification.py delete mode 100644 packages/discovery-provider/src/queries/get_aggregate_route_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_app_name_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_attestation.py delete mode 100644 packages/discovery-provider/src/queries/get_challenges.py delete mode 100644 packages/discovery-provider/src/queries/get_collection_library.py delete mode 100644 packages/discovery-provider/src/queries/get_follow_intersection_users.py delete mode 100644 packages/discovery-provider/src/queries/get_genre_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_genre_metrics_unit_test.py delete mode 100644 packages/discovery-provider/src/queries/get_historical_route_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_notifications.py delete mode 100644 packages/discovery-provider/src/queries/get_personal_route_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_plays_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_plays_metrics_unit_test.py delete mode 100644 packages/discovery-provider/src/queries/get_prev_track_entries.py delete mode 100644 packages/discovery-provider/src/queries/get_related_artists.py delete mode 100644 packages/discovery-provider/src/queries/get_remixable_tracks.py delete mode 100644 packages/discovery-provider/src/queries/get_remixes_of.py delete mode 100644 packages/discovery-provider/src/queries/get_repost_feed_for_user.py delete mode 100644 packages/discovery-provider/src/queries/get_tips.py delete mode 100644 packages/discovery-provider/src/queries/get_top_genre_users.py delete mode 100644 packages/discovery-provider/src/queries/get_top_listeners_for_track.py delete mode 100644 packages/discovery-provider/src/queries/get_top_user_track_tags.py delete mode 100644 packages/discovery-provider/src/queries/get_top_users.py delete mode 100644 packages/discovery-provider/src/queries/get_total_plays.py delete mode 100644 packages/discovery-provider/src/queries/get_trailing_metrics.py delete mode 100644 packages/discovery-provider/src/queries/get_undisbursed_challenges.py delete mode 100644 packages/discovery-provider/src/queries/get_usdc_purchases.py delete mode 100644 packages/discovery-provider/src/queries/get_usdc_transactions_history.py delete mode 100644 packages/discovery-provider/src/queries/get_user_listen_counts_monthly.py delete mode 100644 packages/discovery-provider/src/queries/get_user_listening_history.py delete mode 100644 packages/discovery-provider/src/queries/get_user_playlist_update.py delete mode 100644 packages/discovery-provider/src/queries/get_user_signals.py delete mode 100644 packages/discovery-provider/src/queries/get_users_account.py diff --git a/packages/discovery-provider/integration_tests/challenges/test_challenges.py b/packages/discovery-provider/integration_tests/challenges/test_challenges.py deleted file mode 100644 index ca849af4f19..00000000000 --- a/packages/discovery-provider/integration_tests/challenges/test_challenges.py +++ /dev/null @@ -1,569 +0,0 @@ -import logging -from datetime import datetime, timezone -from typing import Dict, List, Optional - -from sqlalchemy.orm.session import Session - -from integration_tests.queries.test_get_challenges import DefaultUpdater -from integration_tests.utils import populate_mock_db_blocks -from src.challenges.challenge import ( - ChallengeManager, - ChallengeUpdater, - FullEventMetadata, -) -from src.challenges.challenge_event_bus import ChallengeEventBus -from src.models.indexing.block import Block -from src.models.rewards.challenge import Challenge, ChallengeType -from src.models.rewards.user_challenge import UserChallenge -from src.queries.get_challenges import get_challenges -from src.utils.config import shared_config -from src.utils.db_session import get_db -from src.utils.helpers import model_to_dictionary -from src.utils.redis_connection import get_redis - -logger = logging.getLogger(__name__) - -AGGREGATE_CHALLENGE_REWARD_AMOUNT = 5 -AGGREGATE_CHALLENGE_STEP_COUNT = 5 -TEST_BLOCK_DATETIME = datetime.now() - - -def get_created_at(): - return datetime.strptime( - "2023-10-13 11:15:10.627328+00", "%Y-%m-%d %H:%M:%S.%f+00" - ).replace(tzinfo=timezone.utc) - - -def setup_challenges(app): - with app.app_context(): - db = get_db() - challenges = [ - Challenge( - id="test_challenge_1", - type=ChallengeType.numeric, - amount="5", - step_count=3, - active=True, - starting_block=100, - ), - Challenge( - id="test_challenge_2", - type=ChallengeType.boolean, - amount="5", - active=True, - starting_block=100, - ), - Challenge( - id="test_challenge_3", - type=ChallengeType.aggregate, - amount=str(AGGREGATE_CHALLENGE_REWARD_AMOUNT), - step_count=AGGREGATE_CHALLENGE_REWARD_AMOUNT - * AGGREGATE_CHALLENGE_STEP_COUNT, - active=True, - starting_block=100, - ), - Challenge( - id="some_inactive_challenge", - type=ChallengeType.numeric, - amount="5", - step_count=5, - active=False, - starting_block=0, - ), - ] - user_challenges = [ - UserChallenge( - challenge_id="test_challenge_1", - user_id=1, - specifier="1", - is_complete=False, - current_step_count=1, - amount=5, - created_at=get_created_at(), - ), - UserChallenge( - challenge_id="test_challenge_1", - user_id=2, - specifier="2", - is_complete=True, - completed_at=TEST_BLOCK_DATETIME, - current_step_count=3, - completed_blocknumber=100, - amount=5, - created_at=get_created_at(), - ), - UserChallenge( - challenge_id="test_challenge_1", - user_id=3, - specifier="3", - current_step_count=2, - is_complete=False, - amount=5, - created_at=get_created_at(), - ), - UserChallenge( - challenge_id="test_challenge_2", - user_id=4, - specifier="4", - is_complete=True, - completed_at=TEST_BLOCK_DATETIME, - amount=5, - created_at=get_created_at(), - ), - UserChallenge( - challenge_id="test_challenge_1", - user_id=5, - specifier="5", - is_complete=False, - current_step_count=2, - amount=5, - created_at=get_created_at(), - ), - ] - - with db.scoped_session() as session: - # Wipe any existing challenges in the DB from running migrations, etc - session.query(Challenge).delete() - session.add_all(challenges) - session.flush() - session.add_all(user_challenges) - - -class TestUpdater(ChallengeUpdater): - def update_user_challenges( - self, - session: Session, - event: str, - user_challenges: List[UserChallenge], - step_count: Optional[int], - event_metadatas: List[FullEventMetadata], - starting_block: Optional[int], - ): - for user_challenge in user_challenges: - if step_count is not None and user_challenge.current_step_count is not None: - user_challenge.current_step_count += 1 - if user_challenge.current_step_count >= step_count: - user_challenge.is_complete = True - - -def test_handle_event(app): - setup_challenges(app) - - with app.app_context(): - db = get_db() - - populate_mock_db_blocks(db, 99, 110) - - with db.scoped_session() as session: - my_challenge = ChallengeManager("test_challenge_1", TestUpdater()) - # First try an event with a insufficient block_number - # to ensure that nothing happens - my_challenge.process( - session, - "test_event", - [ - { - "user_id": 1, - "block_number": 99, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - ], - ) - session.flush() - actual = ( - session.query(UserChallenge) - .filter( - UserChallenge.challenge_id == "test_challenge_1", - UserChallenge.user_id == 1, - ) - .first() - ) - expected = { - "challenge_id": "test_challenge_1", - "user_id": 1, - "specifier": "1", - "is_complete": False, - "completed_at": None, - "current_step_count": 1, - "completed_blocknumber": None, - "amount": 5, - "created_at": get_created_at(), - } - assert model_to_dictionary(actual) == expected - - # Now process events and make sure things change as expected - my_challenge.process( - session, - "test_event", - [ - { - "user_id": 1, - "block_number": 100, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - { - "user_id": 2, - "block_number": 100, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - { - "user_id": 3, - "block_number": 100, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - # Attempt to add id 6 twice to - # ensure that it doesn't cause a collision - { - "user_id": 6, - "block_number": 100, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - { - "user_id": 6, - "block_number": 100, - "block_datetime": TEST_BLOCK_DATETIME, - "extra": {}, - }, - ], - ) - session.flush() - - updated_complete = ( - session.query(UserChallenge) - .filter(UserChallenge.challenge_id == "test_challenge_1") - .all() - ) - res_dicts = list(map(model_to_dictionary, updated_complete)) - expected = [ - # Should have incremented step count + 1 - { - "challenge_id": "test_challenge_1", - "user_id": 1, - "specifier": "1", - "is_complete": False, - "completed_at": None, - "current_step_count": 2, - "completed_blocknumber": None, - "amount": 5, - "created_at": get_created_at(), - }, - # Should be unchanged b/c it was already complete - { - "challenge_id": "test_challenge_1", - "user_id": 2, - "specifier": "2", - "is_complete": True, - "completed_at": TEST_BLOCK_DATETIME, - "current_step_count": 3, - "completed_blocknumber": 100, - "amount": 5, - "created_at": get_created_at(), - }, - # Should be newly complete - { - "challenge_id": "test_challenge_1", - "user_id": 3, - "specifier": "3", - "is_complete": True, - "completed_at": TEST_BLOCK_DATETIME, - "current_step_count": 3, - "completed_blocknumber": 100, - "amount": 5, - "created_at": get_created_at(), - }, - # Should be untouched bc user 5 wasn't included - { - "challenge_id": "test_challenge_1", - "user_id": 5, - "specifier": "5", - "is_complete": False, - "completed_at": None, - "current_step_count": 2, - "completed_blocknumber": None, - "amount": 5, - "created_at": get_created_at(), - }, - # Should have created a brand new user 6 - { - "challenge_id": "test_challenge_1", - "user_id": 6, - "specifier": "6", - "is_complete": False, - "completed_at": None, - "current_step_count": 1, - "completed_blocknumber": None, - "amount": 5, - }, - ] - # the last challenge was just created so we don't know the created_at time - del res_dicts[-1]["created_at"] - assert expected == res_dicts - - -class AggregateUpdater(ChallengeUpdater): - def update_user_challenges( - self, - session: Session, - event: str, - user_challenges: List[UserChallenge], - step_count: Optional[int], - event_metadatas: List[FullEventMetadata], - starting_block: Optional[int], - ): - pass - - def generate_specifier(self, session: Session, user_id: int, extra: Dict) -> str: - return f"{user_id}-{extra['referred_id']}" - - -REDIS_URL = shared_config["redis"]["url"] - - -def test_aggregates(app): - setup_challenges(app) - - with app.app_context(): - db = get_db() - - redis_conn = get_redis() - - with db.scoped_session() as session: - bus = ChallengeEventBus(redis_conn) - agg_challenge = ChallengeManager("test_challenge_3", AggregateUpdater()) - agg_challenge.process(session, "test_event", []) - TEST_EVENT = "TEST_EVENT" - - bus.register_listener( - TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater()) - ) - bus.register_listener( - TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater()) - ) - # - Multiple events with the same user_id but diff specifiers get created - bus.register_listener(TEST_EVENT, agg_challenge) - bus.dispatch( - TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 2, "amount": 4} - ) - bus.dispatch( - TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 3, "amount": 6} - ) - bus.flush() - bus.process_events(session) - state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) - assert len(state) == 2 - # Test different amounts for the same challenge - assert state[0].amount == 4 - assert state[1].amount == 6 - - # Also make sure the thing is incomplete - res = get_challenges(1, False, session, bus) - agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] - assert agg_chal["is_complete"] == False - - # - Multiple events with the same specifier get deduped - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 4}) - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 4}) - bus.flush() - bus.process_events(session) - state = agg_challenge.get_user_challenge_state(session, ["1-4"]) - state = agg_challenge.get_user_challenge_state(session, ["1-4"]) - assert len(state) == 1 - - def get_user_challenges(): - return ( - session.query(UserChallenge) - .filter( - UserChallenge.challenge_id == "test_challenge_3", - UserChallenge.user_id == 1, - ) - .all() - ) - - # - If we've maxed the # of challenges, don't create any more - # (AGGREGATE_CHALLENGE_STEP_COUNT = 5) - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 5}) - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 6}) - bus.flush() - bus.process_events(session) - assert len(get_user_challenges()) == AGGREGATE_CHALLENGE_STEP_COUNT - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 7}) - bus.flush() - bus.process_events(session) - assert len(get_user_challenges()) == AGGREGATE_CHALLENGE_STEP_COUNT - - # Test get_challenges - res = get_challenges(1, False, session, bus) - agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] - assert agg_chal["is_complete"] == True - # Assert all user challenges have proper finishing block # - user_challenges = get_user_challenges() - for uc in user_challenges: - assert uc.completed_blocknumber == 100 - - -def test_in_memory_queue(app): - setup_challenges(app) - - with app.app_context(): - db = get_db() - - redis_conn = get_redis() - - bus = ChallengeEventBus(redis_conn) - with db.scoped_session() as session, bus.use_scoped_dispatch_queue(): - agg_challenge = ChallengeManager("test_challenge_3", AggregateUpdater()) - agg_challenge.process(session, "test_event", []) - TEST_EVENT = "TEST_EVENT" - - bus.register_listener( - TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater()) - ) - bus.register_listener( - TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater()) - ) - # - Multiple events with the same user_id but diff specifiers get created - bus.register_listener(TEST_EVENT, agg_challenge) - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 2}) - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {"referred_id": 3}) - bus.process_events(session) - - # no events should be processed because we haven't dispatched yet - state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) - assert len(state) == 0 - - bus.process_events(session) - state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) - assert len(state) == 2 - # Also make sure the thing is incomplete - res = get_challenges(1, False, session, bus) - agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] - assert agg_chal["is_complete"] == False - - redis_conn = get_redis() - - -def test_inactive_challenge(app): - setup_challenges(app) - with app.app_context(): - db = get_db() - - redis_conn = get_redis() - - bus = ChallengeEventBus(redis_conn) - with db.scoped_session() as session: - mgr = ChallengeManager("some_inactive_challenge", DefaultUpdater()) - TEST_EVENT = "TEST_EVENT" - bus.register_listener(TEST_EVENT, mgr) - with bus.use_scoped_dispatch_queue(): - bus.dispatch(TEST_EVENT, 100, TEST_BLOCK_DATETIME, 1, {}) - bus.process_events(session) - state = mgr.get_user_challenge_state(session, ["1"]) - # We should not have any UserChallenges created for the - # inactive challenge!! - assert len(state) == 0 - - -def test_rejects_invalid_events(app): - setup_challenges(app) - with app.app_context(): - db = get_db() - - redis_conn = get_redis() - - bus = ChallengeEventBus(redis_conn) - with db.scoped_session() as session: - mgr = ChallengeManager("test_challenge_1", DefaultUpdater()) - TEST_EVENT = "TEST_EVENT" - bus.register_listener(TEST_EVENT, mgr) - with bus.use_scoped_dispatch_queue(): - bus.dispatch(TEST_EVENT, None, TEST_BLOCK_DATETIME, 1) - bus.dispatch(TEST_EVENT, 1, TEST_BLOCK_DATETIME, None) - bus.dispatch(TEST_EVENT, 1, TEST_BLOCK_DATETIME, 1, 1) - (count, did_error) = bus.process_events(session) - assert count == 0 - assert did_error == False - - -class BrokenUpdater(ChallengeUpdater): - # This should trigger a postgres exceptions - def on_after_challenge_creation(self, session, metadatas: List[FullEventMetadata]): - block1 = Block(blockhash="0x99", number=1) - block2 = Block(blockhash="0x99", number=1) - session.add_all([block1, block2]) - session.flush() - - -def test_catches_exceptions_in_single_processor(app): - """Ensure that if a single processor fails, the others still succeed""" - with app.app_context(): - db = get_db() - - redis_conn = get_redis() - - bus = ChallengeEventBus(redis_conn) - with db.scoped_session() as session: - session.add_all( - [ - Challenge( - id="test_challenge_1", - type=ChallengeType.numeric, - amount="5", - step_count=3, - active=True, - ), - Challenge( - id="test_challenge_2", - type=ChallengeType.numeric, - amount="5", - step_count=3, - active=True, - ), - ] - ) - session.commit() - - correct_manager = ChallengeManager("test_challenge_1", DefaultUpdater()) - broken_manager = ChallengeManager("test_challenge_2", BrokenUpdater()) - TEST_EVENT = "TEST_EVENT" - TEST_EVENT_2 = "TEST_EVENT_2" - bus.register_listener(TEST_EVENT, correct_manager) - bus.register_listener(TEST_EVENT_2, broken_manager) - - with bus.use_scoped_dispatch_queue(): - # dispatch the broken one first - bus.dispatch(TEST_EVENT_2, 101, TEST_BLOCK_DATETIME, 1) - bus.dispatch(TEST_EVENT, 101, TEST_BLOCK_DATETIME, 1) - try: - bus.process_events(session) - except: - # pylint: disable=W0707 - raise Exception("Shouldn't have propogated error!") - challenge_1_state = correct_manager.get_user_challenge_state(session, ["1"]) - # Make sure that the 'correct_manager' still executes - assert len(challenge_1_state) == 1 - assert challenge_1_state[0].current_step_count == 1 - # Make sure broken manager didn't do anything - challenge_2_state = broken_manager.get_user_challenge_state(session, ["1"]) - assert len(challenge_2_state) == 0 - - # Try the other order - with bus.use_scoped_dispatch_queue(): - # dispatch the correct one first - bus.dispatch(TEST_EVENT, 101, TEST_BLOCK_DATETIME, 1) - bus.dispatch(TEST_EVENT_2, 101, TEST_BLOCK_DATETIME, 1) - try: - bus.process_events(session) - except: - # pylint: disable=W0707 - raise Exception("Shouldn't have propogated error!") - challenge_1_state = correct_manager.get_user_challenge_state(session, ["1"]) - assert len(challenge_1_state) == 1 - assert challenge_1_state[0].current_step_count == 2 - # Make sure broken manager didn't do anything - challenge_2_state = broken_manager.get_user_challenge_state(session, ["1"]) - assert len(challenge_2_state) == 0 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_app_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_aggregate_app_metrics.py deleted file mode 100644 index ce37466e974..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_app_metrics.py +++ /dev/null @@ -1,152 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_app_name_metrics import ( - AggregateDailyAppNameMetric, -) -from src.models.metrics.aggregate_monthly_app_name_metrics import ( - AggregateMonthlyAppNameMetric, -) -from src.queries.get_app_name_metrics import _get_aggregate_app_metrics -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) - - -def test_get_aggregate_app_metrics_week(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyAppNameMetric( - application_name="will-not-return-because-too-old", - count=3, - timestamp=today - timedelta(days=8), - ), - AggregateDailyAppNameMetric( - application_name="top-app", - count=4, - timestamp=yesterday - timedelta(days=1), - ), - AggregateDailyAppNameMetric( - application_name="will-not-return-because-outside-limit", - count=1, - timestamp=yesterday, - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=5, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="top-app", count=3, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="will-not-return-because-too-recent", - count=3, - timestamp=today, - ), - ] - ) - - aggregate_metrics = _get_aggregate_app_metrics(session, "week", limit) - - assert len(aggregate_metrics) == limit - assert aggregate_metrics[0]["name"] == "top-app" - assert aggregate_metrics[0]["count"] == 7 - assert aggregate_metrics[1]["name"] == "best-app" - assert aggregate_metrics[1]["count"] == 5 - - -def test_get_aggregate_app_metrics_month(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyAppNameMetric( - application_name="will-not-return-because-too-old", - count=20, - timestamp=today - timedelta(days=31), - ), - AggregateDailyAppNameMetric( - application_name="best-app", - count=20, - timestamp=today - timedelta(days=31), - ), - AggregateDailyAppNameMetric( - application_name="will-not-return-because-outside-limit", - count=1, - timestamp=yesterday, - ), - AggregateDailyAppNameMetric( - application_name="top-app", - count=5, - timestamp=yesterday - timedelta(days=8), - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=5, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="top-app", count=7, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="will-not-return-because-too-recent", - count=20, - timestamp=today, - ), - ] - ) - - aggregate_metrics = _get_aggregate_app_metrics(session, "month", limit) - - assert len(aggregate_metrics) == limit - assert aggregate_metrics[0]["name"] == "top-app" - assert aggregate_metrics[0]["count"] == 12 - assert aggregate_metrics[1]["name"] == "best-app" - assert aggregate_metrics[1]["count"] == 5 - - -def test_get_aggregate_app_metrics_all_time(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateMonthlyAppNameMetric( - application_name="awesome-app", - count=6, - timestamp=today - timedelta(days=367), - ), - AggregateMonthlyAppNameMetric( - application_name="will-not-return-because-outside-limit", - count=1, - timestamp=yesterday, - ), - AggregateMonthlyAppNameMetric( - application_name="best-app", count=5, timestamp=yesterday - ), - AggregateMonthlyAppNameMetric( - application_name="top-app", count=15, timestamp=yesterday - ), - AggregateMonthlyAppNameMetric( - application_name="awesome-app", count=7, timestamp=yesterday - ), - AggregateMonthlyAppNameMetric( - application_name="will-not-return-because-too-recent", - count=20, - timestamp=today, - ), - ] - ) - - aggregate_metrics = _get_aggregate_app_metrics(session, "all_time", limit) - - assert len(aggregate_metrics) == limit - assert aggregate_metrics[0]["name"] == "top-app" - assert aggregate_metrics[0]["count"] == 15 - assert aggregate_metrics[1]["name"] == "awesome-app" - assert aggregate_metrics[1]["count"] == 13 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics.py deleted file mode 100644 index 22fbbfe5558..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics.py +++ /dev/null @@ -1,215 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.queries.get_aggregate_route_metrics import _get_aggregate_route_metrics -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) - - -def test_get_aggregate_route_metrics_week(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, summed_count=2, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=yesterday - timedelta(days=1) - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, summed_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics( - count=4, timestamp=yesterday - timedelta(days=1) - ), - AggregateDailyTotalUsersMetrics(count=6, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=8, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics(session, "week", "day") - - assert len(aggregate_metrics) == 2 - assert aggregate_metrics[0]["unique_count"] == 2 - assert aggregate_metrics[0]["summed_unique_count"] == 3 - assert aggregate_metrics[0]["total_count"] == 4 - assert aggregate_metrics[1]["unique_count"] == 3 - assert aggregate_metrics[1]["summed_unique_count"] == 4 - assert aggregate_metrics[1]["total_count"] == 6 - - -def test_get_aggregate_route_metrics_month_daily_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, summed_count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, summed_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyTotalUsersMetrics( - count=4, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics(count=6, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=8, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics(session, "month", "day") - - assert len(aggregate_metrics) == 2 - assert aggregate_metrics[0]["unique_count"] == 2 - assert aggregate_metrics[0]["summed_unique_count"] == 3 - assert aggregate_metrics[0]["total_count"] == 4 - assert aggregate_metrics[1]["unique_count"] == 3 - assert aggregate_metrics[1]["summed_unique_count"] == 4 - assert aggregate_metrics[1]["total_count"] == 6 - - -def test_get_aggregate_route_metrics_month_weekly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, summed_count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, summed_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyTotalUsersMetrics( - count=4, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics(count=6, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=8, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics(session, "month", "week") - - assert len(aggregate_metrics) == 2 - assert aggregate_metrics[0]["unique_count"] == 2 - assert aggregate_metrics[0]["summed_unique_count"] == 3 - assert aggregate_metrics[0]["total_count"] == 4 - assert aggregate_metrics[1]["unique_count"] == 3 - assert aggregate_metrics[1]["summed_unique_count"] == 4 - assert aggregate_metrics[1]["total_count"] == 6 - - -def test_get_aggregate_route_metrics_all_time_monthly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateMonthlyUniqueUsersMetric( - count=1, summed_count=2, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyUniqueUsersMetric( - count=2, summed_count=3, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyUniqueUsersMetric( - count=3, summed_count=4, timestamp=today - ), - AggregateMonthlyTotalUsersMetric( - count=2, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyTotalUsersMetric( - count=4, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyTotalUsersMetric(count=6, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics(session, "all_time", "month") - - assert len(aggregate_metrics) == 2 - assert aggregate_metrics[0]["unique_count"] == 1 - assert aggregate_metrics[0]["summed_unique_count"] == 2 - assert aggregate_metrics[0]["total_count"] == 2 - assert aggregate_metrics[1]["unique_count"] == 2 - assert aggregate_metrics[1]["summed_unique_count"] == 3 - assert aggregate_metrics[1]["total_count"] == 4 - - -def test_get_aggregate_route_metrics_all_time_weekly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, summed_count=2, timestamp=today - timedelta(days=367) - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=today - timedelta(days=367) - ), - AggregateDailyTotalUsersMetrics(count=4, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=6, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics(session, "all_time", "week") - - assert len(aggregate_metrics) == 2 - assert aggregate_metrics[0]["unique_count"] == 1 - assert aggregate_metrics[0]["summed_unique_count"] == 2 - assert aggregate_metrics[0]["total_count"] == 2 - assert aggregate_metrics[1]["unique_count"] == 2 - assert aggregate_metrics[1]["summed_unique_count"] == 3 - assert aggregate_metrics[1]["total_count"] == 4 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics_trailing_month.py b/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics_trailing_month.py deleted file mode 100644 index 61a81e9fe75..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_aggregate_route_metrics_trailing_month.py +++ /dev/null @@ -1,51 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.queries.get_trailing_metrics import _get_aggregate_route_metrics_trailing -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) - - -def test_get_aggregate_route_metrics_trailing_month(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, summed_count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=today - timedelta(days=30) - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, summed_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyTotalUsersMetrics( - count=4, timestamp=today - timedelta(days=30) - ), - AggregateDailyTotalUsersMetrics(count=6, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=8, timestamp=today), - ] - ) - - aggregate_metrics = _get_aggregate_route_metrics_trailing(session, "month") - - assert aggregate_metrics["unique_count"] == 5 - assert aggregate_metrics["summed_unique_count"] == 7 - assert aggregate_metrics["total_count"] == 10 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_attestation.py b/packages/discovery-provider/integration_tests/queries/test_get_attestation.py deleted file mode 100644 index 009ad3b27cf..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_attestation.py +++ /dev/null @@ -1,326 +0,0 @@ -from unittest.mock import patch - -import pytest -from eth_keys import keys -from eth_utils.conversions import to_bytes -from hexbytes import HexBytes -from solders.pubkey import Pubkey -from web3 import Web3 - -from integration_tests.queries.test_get_challenges import setup_db -from integration_tests.utils import populate_mock_db -from src.models.rewards.challenge import ChallengeType -from src.queries.get_attestation import ( - ADD_SENDER_MESSAGE_PREFIX, - POOL_EXHAUSTED, - REWARDS_MANAGER_ACCOUNT, - Attestation, - AttestationError, - get_attestation, - get_create_sender_attestation, -) -from src.tasks.index_oracles import oracle_addresses_key -from src.utils.config import shared_config -from src.utils.db_session import get_db -from src.utils.redis_connection import get_redis - -REDIS_URL = shared_config["redis"]["url"] -redis_handle = get_redis() - - -DUMMY_ORACLE_ADDRESS = "0x32a10e91820fd10366AC363eD0DEa40B2e598D22" - - -def test_get_attestation(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - setup_db(session) - - # Tests: - # - Happy path - # - No user_challenge - # - Challenge not finished - # - No disbursement - # - Invalid oracle - oracle_address = DUMMY_ORACLE_ADDRESS - redis_handle.set(oracle_addresses_key, oracle_address) - - delegate_owner_wallet, signature = get_attestation( - session, - user_id=1, - challenge_id="boolean_challenge_2", - oracle_address=oracle_address, - specifier="1", - ) - - attestation = Attestation( - amount="5", - oracle_address=oracle_address, - # Wallet from user 2 in setup_db - user_address="0x38C68fF3926bf4E68289672F75ee1543117dD9B3", - challenge_id="boolean_challenge_2", - challenge_specifier="1", - ) - - # Test happy path - - # confirm the attestation is what we think it should be - config_owner_wallet = shared_config["delegate"]["owner_wallet"] - config_private_key = shared_config["delegate"]["private_key"] - - # Ensure we returned the correct owner wallet - assert delegate_owner_wallet == config_owner_wallet - - # Ensure we can derive the owner wallet from the signed stringified attestation - attestation_bytes = attestation.get_attestation_bytes() - to_sign_hash = Web3.keccak(attestation_bytes) - private_key = keys.PrivateKey(HexBytes(config_private_key)) - public_key = keys.PublicKey.from_private(private_key) - signture_bytes = to_bytes(hexstr=signature) - msg_signature = keys.Signature(signature_bytes=signture_bytes, vrs=None) - - recovered_pubkey = public_key.recover_from_msg_hash( - message_hash=to_sign_hash, signature=msg_signature - ) - - assert ( - Web3.to_checksum_address(recovered_pubkey.to_address()) - == config_owner_wallet - ) - - # Test no matching user challenge - with pytest.raises(AttestationError): - get_attestation( - session, - user_id=1, - challenge_id="boolean_challenge_2", - oracle_address=oracle_address, - specifier="xyz", - ) - - # Test challenge not finished - with pytest.raises(AttestationError): - get_attestation( - session, - user_id=1, - challenge_id="boolean_challenge_3", - oracle_address=oracle_address, - specifier="1", - ) - - # Test challenge already disbursed - with pytest.raises(AttestationError): - get_attestation( - session, - user_id=1, - challenge_id="boolean_challenge_1", - oracle_address=oracle_address, - specifier="1", - ) - - # Test with bad AAO - with pytest.raises(AttestationError): - get_attestation( - session, - user_id=1, - challenge_id="boolean_challenge_2", - oracle_address="wrong_oracle_address", - specifier="1", - ) - - -def test_get_attestation_weekly_pool_exhausted(app): - with app.app_context(): - db = get_db() - - entities = { - "users": [ - {"user_id": 1, "wallet": "0x38C68fF3926bf4E68289672F75ee1543117dDAAA"}, - {"user_id": 2, "wallet": "0x38C68fF3926bf4E68289672F75ee1543117dD9B3"}, - {"user_id": 3, "wallet": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"}, - ], - "challenges": [ - { - "id": "not-exhausted", - "type": ChallengeType.numeric, - "active": True, - "amount": 5, - "weekly_pool": 10, - }, - { - "id": "exhausted", - "type": ChallengeType.numeric, - "active": True, - "amount": 10, - "weekly_pool": 20, - }, - ], - "user_challenges": [ - { - "challenge_id": "not-exhausted", - "user_id": 1, - "specifier": "1", - "is_complete": True, - "amount": 5, - }, - { - "challenge_id": "not-exhausted", - "user_id": 2, - "specifier": "2", - "is_complete": True, - "amount": 5, - }, - { - "challenge_id": "exhausted", - "user_id": 1, - "specifier": "1", - "is_complete": True, - "amount": 10, - }, - { - "challenge_id": "exhausted", - "user_id": 2, - "specifier": "2", - "is_complete": True, - "amount": 10, - }, - # User 3 has completed challenge `exhausted` but will not be able to disburse - { - "challenge_id": "exhausted", - "user_id": 3, - "specifier": "3", - "is_complete": True, - "amount": 10, - }, - ], - "challenge_disbursements": [ - { - "challenge_id": "not-exhausted", - "specifier": "1", - "user_id": 1, - "amount": "500000000", - }, - { - "challenge_id": "exhausted", - "specifier": "1", - "user_id": 1, - "amount": "1000000000", - }, - { - "challenge_id": "exhausted", - "specifier": "2", - "user_id": 2, - "amount": "1000000000", - }, - ], - } - populate_mock_db(db, entities) - with db.scoped_session() as session: - oracle_address = DUMMY_ORACLE_ADDRESS - redis_handle.set(oracle_addresses_key, oracle_address) - - delegate_owner_wallet, signature = get_attestation( - session, - user_id=2, - challenge_id="not-exhausted", - oracle_address=oracle_address, - specifier="2", - ) - - attestation = Attestation( - amount="5", - oracle_address=oracle_address, - user_address=entities["users"][1]["wallet"], - challenge_id="not-exhausted", - challenge_specifier="2", - ) - - # User 2 should still be able to disburse the non-exhausted challenge - - # confirm the attestation is what we think it should be - config_owner_wallet = shared_config["delegate"]["owner_wallet"] - config_private_key = shared_config["delegate"]["private_key"] - - # Ensure we returned the correct owner wallet - assert delegate_owner_wallet == config_owner_wallet - - # Ensure we can derive the owner wallet from the signed stringified attestation - attestation_bytes = attestation.get_attestation_bytes() - to_sign_hash = Web3.keccak(attestation_bytes) - private_key = keys.PrivateKey(HexBytes(config_private_key)) - public_key = keys.PublicKey.from_private(private_key) - signture_bytes = to_bytes(hexstr=signature) - msg_signature = keys.Signature(signature_bytes=signture_bytes, vrs=None) - - recovered_pubkey = public_key.recover_from_msg_hash( - message_hash=to_sign_hash, signature=msg_signature - ) - - assert ( - Web3.to_checksum_address(recovered_pubkey.to_address()) - == config_owner_wallet - ) - - with pytest.raises(AttestationError) as e: - get_attestation( - session, - user_id=3, - challenge_id="exhausted", - oracle_address=oracle_address, - specifier="3", - ) - assert str(e.value) == POOL_EXHAUSTED - - -@pytest.fixture -def patch_get_all_nodes(): - with patch( - "src.queries.get_attestation.get_all_discovery_nodes_cached", - return_value=[ - {"delegateOwnerWallet": "0x94e140D27F3d5EE9EcA0109A71CcBa0109964DCa"} - ], - ): - yield - - -def test_get_create_sender_attestation(app, patch_get_all_nodes): - new_sender_address = "0x94e140D27F3d5EE9EcA0109A71CcBa0109964DCa" - owner_wallet, sender_attestation = get_create_sender_attestation(new_sender_address) - - # confirm the attestation is what we think it should be - config_owner_wallet = shared_config["delegate"]["owner_wallet"] - config_private_key = shared_config["delegate"]["private_key"] - - # Ensure we returned the correct owner wallet - assert owner_wallet == config_owner_wallet - - # Ensure we can derive the owner wallet from the signed stringified attestation - items = [ - to_bytes(text=ADD_SENDER_MESSAGE_PREFIX), - bytes(Pubkey.from_string(REWARDS_MANAGER_ACCOUNT)), - to_bytes(hexstr=new_sender_address), - ] - attestation_bytes = to_bytes(text="").join(items) - to_sign_hash = Web3.keccak(attestation_bytes) - private_key = keys.PrivateKey(HexBytes(config_private_key)) - public_key = keys.PublicKey.from_private(private_key) - signture_bytes = to_bytes(hexstr=sender_attestation) - msg_signature = keys.Signature(signature_bytes=signture_bytes, vrs=None) - - recovered_pubkey = public_key.recover_from_msg_hash( - message_hash=to_sign_hash, signature=msg_signature - ) - - assert ( - Web3.to_checksum_address(recovered_pubkey.to_address()) == config_owner_wallet - ) - - -def test_get_create_sender_attestation_not_registered(app, patch_get_all_nodes): - new_sender_address = "0x04e140D27F3d5EE9EcA0109A71CcBa0109964DCa" - with pytest.raises( - Exception, - match=r"Expected 0x04e140D27F3d5EE9EcA0109A71CcBa0109964DCa to be registered on chain", - ): - get_create_sender_attestation(new_sender_address) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_challenges.py b/packages/discovery-provider/integration_tests/queries/test_get_challenges.py deleted file mode 100644 index 6b7662fed93..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_challenges.py +++ /dev/null @@ -1,886 +0,0 @@ -from datetime import datetime, timedelta -from typing import List, Optional - -from sqlalchemy.orm import Session - -from src.challenges.challenge import ( - ChallengeManager, - ChallengeUpdater, - FullEventMetadata, -) -from src.challenges.challenge_event_bus import ChallengeEventBus -from src.challenges.listen_streak_endless_challenge import ( - listen_streak_endless_challenge_manager, -) -from src.challenges.referral_challenge import ( - referral_challenge_manager, - verified_referral_challenge_manager, -) -from src.models.indexing.block import Block -from src.models.rewards.challenge import Challenge, ChallengeType -from src.models.rewards.challenge_disbursement import ChallengeDisbursement -from src.models.rewards.listen_streak_challenge import ChallengeListenStreak -from src.models.rewards.user_challenge import UserChallenge -from src.models.users.user import User -from src.queries.get_challenges import get_challenges -from src.utils.config import shared_config -from src.utils.db_session import get_db -from src.utils.redis_connection import get_redis - -REDIS_URL = shared_config["redis"]["url"] -DEFAULT_EVENT = "" -AGGREGATE_CHALLENGES_AMOUNT = 5 -AGGREGATE_CHALLENGE_1_STEP_COUNT = 3 -AGGREGATE_CHALLENGE_2_3_STEP_COUNT = 2 - - -class DefaultUpdater(ChallengeUpdater): - def update_user_challenges( - self, - session: Session, - event: str, - user_challenges: List[UserChallenge], - step_count: Optional[int], - event_metadatas: List[FullEventMetadata], - starting_block: Optional[int], - ): - for challenge in user_challenges: - if not challenge.current_step_count: - challenge.current_step_count = 0 - challenge.current_step_count += 1 - if challenge.current_step_count == step_count: - challenge.is_complete = True - - -class NumericCustomUpdater(ChallengeUpdater): - def get_default_metadata(self): - return {"default_state": True} - - def get_metadata(self, session: Session, specifiers: List[str]): - return [{"special_metadata": s} for s in specifiers] - - -def setup_db(session): - blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)] - users = [ - User( - blockhash="0x1", - blocknumber=1, - user_id=1, - is_current=True, - wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3", - created_at=datetime.now(), - updated_at=datetime.now(), - ) - ] - challenges = [ - Challenge( - id="boolean_challenge_1", - type=ChallengeType.boolean, - active=True, - amount="5", - ), - Challenge( - id="boolean_challenge_2", - type=ChallengeType.boolean, - active=True, - amount="5", - ), - Challenge( - id="boolean_challenge_3", - type=ChallengeType.boolean, - active=True, - amount="5", - ), - # No progress on this, but active - # should be returned - Challenge( - id="boolean_challenge_4", - type=ChallengeType.boolean, - active=True, - amount="5", - ), - # Inactive, with no progress - Challenge( - id="boolean_challenge_5", - type=ChallengeType.boolean, - active=False, - amount="5", - ), - # Inactive, WITH progress - Challenge( - id="boolean_challenge_6", - type=ChallengeType.boolean, - active=False, - amount="5", - ), - Challenge( - id="trending_challenge_1", - type=ChallengeType.trending, - active=True, - amount="5", - ), - Challenge( - id="aggregate_challenge_1", - type=ChallengeType.aggregate, - active=True, - amount="5", - step_count=AGGREGATE_CHALLENGE_1_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT, - ), - Challenge( - id="aggregate_challenge_2", - type=ChallengeType.aggregate, - active=True, - amount="5", - step_count=AGGREGATE_CHALLENGE_2_3_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT, - ), - Challenge( - id="aggregate_challenge_3", - type=ChallengeType.aggregate, - active=True, - amount="5", - step_count=AGGREGATE_CHALLENGE_2_3_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT, - ), - Challenge( - id="trending_1", type=ChallengeType.trending, active=True, amount="5" - ), - Challenge( - id="trending_2", type=ChallengeType.trending, active=True, amount="5" - ), - Challenge( - id="trending_3", type=ChallengeType.trending, active=True, amount="5" - ), - ] - user_challenges = [ - # Finished the first challenge, disbursed - UserChallenge( - challenge_id="boolean_challenge_1", - user_id=1, - specifier="1", - is_complete=True, - amount=5, - completed_at=datetime.now(), - ), - # Did finish the second challenge, did not disburse - UserChallenge( - challenge_id="boolean_challenge_2", - user_id=1, - specifier="1", - is_complete=True, - amount=5, - completed_at=datetime.now(), - ), - # Did not finish challenge 3 - UserChallenge( - challenge_id="boolean_challenge_3", - user_id=1, - specifier="1", - is_complete=False, - amount=5, - completed_at=datetime.now(), - ), - # Inactive challenge - UserChallenge( - challenge_id="boolean_challenge_6", - user_id=1, - specifier="1", - is_complete=True, - amount=5, - completed_at=datetime.now(), - ), - UserChallenge( - challenge_id="aggregate_challenge_1", - user_id=1, - specifier="1-2", # compound specifiers, like if user1 invites user2 - is_complete=True, - amount=AGGREGATE_CHALLENGES_AMOUNT, - completed_at=datetime.now(), - ), - # Ensure that a non-complete user-challenge isn't counted towards - # aggregate challenge score - UserChallenge( - challenge_id="aggregate_challenge_1", - user_id=1, - specifier="1-3", - is_complete=False, - amount=AGGREGATE_CHALLENGES_AMOUNT, - completed_at=datetime.now(), - ), - UserChallenge( - challenge_id="aggregate_challenge_2", - user_id=1, - specifier="1-2", - is_complete=True, - amount=AGGREGATE_CHALLENGES_AMOUNT, - completed_at=datetime.now(), - ), - UserChallenge( - challenge_id="aggregate_challenge_2", - user_id=1, - specifier="1-3", - is_complete=True, - amount=AGGREGATE_CHALLENGES_AMOUNT, - completed_at=datetime.now(), - ), - # Trending 1 should be finished and included - UserChallenge( - challenge_id="trending_1", - user_id=1, - specifier="06-01-2020", - is_complete=True, - amount=5, - completed_at=datetime.now(), - ), - # Trending 2 should not be included - UserChallenge( - challenge_id="trending_2", - user_id=1, - specifier="06-01-2020", - is_complete=False, - amount=5, - completed_at=datetime.now(), - ), - ] - disbursements = [ - ChallengeDisbursement( - challenge_id="boolean_challenge_1", - user_id=1, - amount="5", - signature="1", - slot=1, - specifier="1", - ) - ] - - # Wipe any existing challenges in the DB from running migrations, etc - session.query(Challenge).delete() - session.commit() - session.add_all(blocks) - session.commit() - session.add_all(users) - session.commit() - session.add_all(challenges) - session.commit() - session.add_all(user_challenges) - session.commit() - session.add_all(disbursements) - - redis_conn = get_redis() - bus = ChallengeEventBus(redis_conn) - challenge_types = [ - "boolean_challenge_1", - "boolean_challenge_2", - "boolean_challenge_3", - "boolean_challenge_4", - "boolean_challenge_5", - "boolean_challenge_6", - "trending_challenge_1", - "aggregate_challenge_1", - "aggregate_challenge_2", - "aggregate_challenge_3", - "trending_1", - "trending_2", - "trending_3", - ] - for ct in challenge_types: - bus.register_listener( - DEFAULT_EVENT, - ChallengeManager(ct, DefaultUpdater()), - ) - return bus - - -def test_get_challenges(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_db(session) - - # Setup registry - - # Try to get the challenges, not historical - res = get_challenges(1, False, session, bus) - - # We don't care about the order of the challenges returned - # so make a map - res_map = {challenge["challenge_id"]: challenge for challenge in res} - # Ensure correct num of challenges - # not returned are: - # - inactive, with no user_challenge - # - inactive, with user_challenge - # - trending, with no user_challenge - # - trending, with one unfinished user challenge (doesn't even make sense, but it could happen) - assert len(res) == 8 - - # Base case - an active, completed, disbursed chal - chal_1 = res_map["boolean_challenge_1"] - assert chal_1["is_disbursed"] - assert chal_1["is_active"] - assert chal_1["is_complete"] - - # Active, complete, non disbursed - chal_2 = res_map["boolean_challenge_2"] - assert chal_2["is_disbursed"] == False - assert chal_2["is_active"] - assert chal_2["is_complete"] - - # active, incomplete, nondisbursed - chal_3 = res_map["boolean_challenge_3"] - assert chal_3["is_disbursed"] == False - assert chal_3["is_active"] - assert chal_3["is_complete"] == False - - # no user progress, but active, so should be returned - chal_4 = res_map["boolean_challenge_4"] - assert chal_4["is_disbursed"] == False - assert chal_4["is_active"] - assert chal_4["is_complete"] == False - - # aggregate challenge with one completion, so not fully complete - chal_agg_1 = res_map["aggregate_challenge_1"] - assert ( - chal_agg_1["is_disbursed"] == False - ) # This field doesn't matter since we can't roll up disbursions - assert chal_agg_1["is_active"] - assert chal_agg_1["is_complete"] == False - assert chal_agg_1["current_step_count"] == 5 - assert ( - chal_agg_1["max_steps"] - == AGGREGATE_CHALLENGE_1_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT - ) - - # aggregate challenge with 2 completions, FULLY complete - chal_agg_2 = res_map["aggregate_challenge_2"] - assert ( - chal_agg_2["is_disbursed"] == False - ) # This field doesn't matter since we can't roll up disbursions - assert chal_agg_2["is_active"] - assert chal_agg_2["is_complete"] == True - assert chal_agg_2["current_step_count"] == 10 - assert ( - chal_agg_2["max_steps"] - == AGGREGATE_CHALLENGE_2_3_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT - ) - - # aggregate challenge with no completions - chal_agg_3 = res_map["aggregate_challenge_3"] - assert ( - chal_agg_3["is_disbursed"] == False - ) # This field doesn't matter since we can't roll up disbursions - assert chal_agg_3["is_active"] - assert chal_agg_3["is_complete"] == False - assert chal_agg_3["current_step_count"] == 0 - assert ( - chal_agg_3["max_steps"] - == AGGREGATE_CHALLENGE_2_3_STEP_COUNT * AGGREGATE_CHALLENGES_AMOUNT - ) - - # complete trending challenge - chal_trend_1 = res_map["trending_1"] - assert chal_trend_1["is_disbursed"] == False - assert chal_trend_1["is_active"] - assert chal_trend_1["is_complete"] - - # Try to get the challenges, this time historical - res = get_challenges(1, True, session, bus) - - # The only difference is that we should have shown - # inactive but complete challenges - assert len(res) == 9 - res_map = {challenge["challenge_id"]: challenge for challenge in res} - historical = res_map["boolean_challenge_6"] - assert historical["is_active"] == False - assert historical["is_complete"] - assert historical["is_disbursed"] == False - - -def setup_extra_metadata_test(session): - blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)] - users = [ - User( - blockhash="0x1", - blocknumber=1, - user_id=1, - is_current=True, - wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3", - created_at=datetime.now(), - updated_at=datetime.now(), - ) - ] - challenges = [ - # Test numeric challenges - # Numeric 1 with default extra data, no completion - Challenge( - id="numeric_1", - type=ChallengeType.numeric, - active=True, - amount="5", - step_count=5, - ), - # Numeric 2 with some extra data - Challenge( - id="numeric_2", - type=ChallengeType.numeric, - active=True, - amount="5", - step_count=5, - ), - ] - - user_challenges = [ - UserChallenge( - challenge_id="numeric_2", - user_id=1, - specifier="1", - is_complete=False, - current_step_count=5, - amount=5, - completed_at=datetime.now(), - ), - ] - - session.query(Challenge).delete() - session.commit() - session.add_all(blocks) - session.commit() - session.add_all(users) - session.commit() - session.add_all(challenges) - session.commit() - session.add_all(user_challenges) - session.commit() - - redis_conn = get_redis() - bus = ChallengeEventBus(redis_conn) - bus.register_listener( - DEFAULT_EVENT, ChallengeManager("numeric_1", NumericCustomUpdater()) - ) - bus.register_listener( - DEFAULT_EVENT, ChallengeManager("numeric_2", NumericCustomUpdater()) - ) - return bus - - -def test_extra_metadata(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_extra_metadata_test(session) - - res = get_challenges(1, False, session, bus) - challenge_1 = [r for r in res if r["challenge_id"] == "numeric_1"][0] - challenge_2 = [r for r in res if r["challenge_id"] == "numeric_2"][0] - assert challenge_1["metadata"] == {"default_state": True} - assert challenge_2["metadata"] == {"special_metadata": "1"} - - -# Testing getting verified/unverified referral challenges - - -def setup_verified_test(session): - # Setup - blocks = [ - Block(blockhash="0x1", number=1, parenthash="", is_current=False), - Block(blockhash="0x2", number=2, parenthash="", is_current=True), - ] - users = [ - User( - blockhash="0x1", - blocknumber=1, - user_id=1, - is_current=True, - wallet="0xFakeWallet1", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - User( - blockhash="0x2", - blocknumber=2, - user_id=2, - is_current=True, - wallet="0xFakeWallet2", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=True, - ), - ] - - challenges = [ - Challenge( - id="r", - type=ChallengeType.aggregate, - active=True, - amount="1", - step_count=5, - ), - Challenge( - id="rv", - type=ChallengeType.aggregate, - active=True, - amount="1", - step_count=500, - ), - ] - - # Wipe any existing challenges in the DB from running migrations, etc - session.query(Challenge).delete() - session.commit() - session.add_all(blocks) - session.commit() - session.add_all(users) - session.add_all(challenges) - session.commit() - - redis_conn = get_redis() - bus = ChallengeEventBus(redis_conn) - bus.register_listener(DEFAULT_EVENT, referral_challenge_manager) - bus.register_listener(DEFAULT_EVENT, verified_referral_challenge_manager) - return bus - - -def test_nonverified_referrals_invisible_to_verified_user(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_verified_test(session) - - non_verified = get_challenges(1, False, session, bus) - assert len(non_verified) == 1 - assert non_verified[0]["challenge_id"] == "r" - - -def test_verified_referrals_invisible_to_nonverified_user(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_verified_test(session) - - verified = get_challenges(2, False, session, bus) - assert len(verified) == 1 - assert verified[0]["challenge_id"] == "rv" - - -# Testing getting listen streak challenges with override - - -def setup_listen_streak_challenge(session): - # Setup - blocks = [ - Block(blockhash="0x1", number=1, parenthash="", is_current=False), - Block(blockhash="0x2", number=2, parenthash="", is_current=False), - Block(blockhash="0x3", number=3, parenthash="", is_current=False), - Block(blockhash="0x4", number=4, parenthash="", is_current=False), - Block(blockhash="0x5", number=5, parenthash="", is_current=False), - Block(blockhash="0x6", number=6, parenthash="", is_current=True), - ] - users = [ - User( - blockhash="0x1", - blocknumber=1, - user_id=1, - is_current=True, - wallet="0xFakeWallet1", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - User( - blockhash="0x2", - blocknumber=2, - user_id=2, - is_current=True, - wallet="0xFakeWallet2", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=True, - ), - User( - blockhash="0x3", - blocknumber=3, - user_id=3, - is_current=True, - wallet="0xFakeWallet3", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - User( - blockhash="0x4", - blocknumber=4, - user_id=4, - is_current=True, - wallet="0xFakeWallet4", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - User( - blockhash="0x5", - blocknumber=5, - user_id=5, - is_current=True, - wallet="0xFakeWallet5", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - User( - blockhash="0x6", - blocknumber=6, - user_id=6, - is_current=True, - wallet="0xFakeWallet6", - created_at=datetime.now(), - updated_at=datetime.now(), - is_verified=False, - ), - ] - - challenges = [ - Challenge( - id="e", - type=ChallengeType.aggregate, - active=True, - amount="1", - step_count=2147483647, - ) - ] - - user_challenges = [ - UserChallenge( - challenge_id="e", - user_id=1, - specifier="1", - is_complete=False, - current_step_count=5, - amount=1, - created_at=datetime.now(), - ), - UserChallenge( - challenge_id="e", - user_id=2, - specifier="2", - is_complete=False, - current_step_count=5, - amount=1, - created_at=datetime.now(), - ), - # User 3 has just started a new streak for the first time - UserChallenge( - challenge_id="e", - user_id=3, - specifier="3x0", - is_complete=False, - current_step_count=3, - amount=7, - created_at=datetime.now(), - ), - # User 4 has an ongoing listen streak of 9 - UserChallenge( - challenge_id="e", - user_id=4, - specifier="4x0", - is_complete=True, - current_step_count=7, - amount=7, - completed_at=datetime.now() - timedelta(days=2), - created_at=datetime.now() - timedelta(days=9), - ), - UserChallenge( - challenge_id="e", - user_id=4, - specifier="4x1", - is_complete=True, - current_step_count=1, - amount=1, - completed_at=datetime.now() - timedelta(days=1), - created_at=datetime.now() - timedelta(days=1), - ), - UserChallenge( - challenge_id="e", - user_id=4, - specifier="4x2", - is_complete=True, - current_step_count=1, - amount=1, - completed_at=datetime.now(), - created_at=datetime.now(), - ), - # User 5 has one complete listen streak of 8, and an in-progress streak of 3 - UserChallenge( - challenge_id="e", - user_id=5, - specifier="5x0", - is_complete=True, - current_step_count=7, - amount=7, - completed_at=datetime.now() - timedelta(days=30), - created_at=datetime.now() - timedelta(days=37), - ), - UserChallenge( - challenge_id="e", - user_id=5, - specifier="5x1", - is_complete=True, - current_step_count=1, - amount=1, - completed_at=datetime.now() - timedelta(days=29), - created_at=datetime.now() - timedelta(days=29), - ), - UserChallenge( - challenge_id="e", - user_id=5, - specifier="5x2", - is_complete=False, - current_step_count=3, - amount=3, - created_at=datetime.now(), - ), - # User 6 has one complete streak of 8, and a broken streak of 2 - UserChallenge( - challenge_id="e", - user_id=6, - specifier="6x0", - is_complete=True, - current_step_count=7, - amount=7, - completed_at=datetime.now() - timedelta(days=30), - created_at=datetime.now() - timedelta(days=37), - ), - UserChallenge( - challenge_id="e", - user_id=6, - specifier="6x1", - is_complete=True, - current_step_count=1, - amount=1, - completed_at=datetime.now() - timedelta(days=29), - created_at=datetime.now() - timedelta(days=29), - ), - UserChallenge( - challenge_id="e", - user_id=6, - specifier="6x2", - is_complete=False, - current_step_count=2, - amount=7, - created_at=datetime.now() - timedelta(days=4), - ), - ] - - listen_streak_challenges = [ - # User 1 has a completed listen streak of 5 - ChallengeListenStreak( - user_id=1, - last_listen_date=datetime.now() - timedelta(hours=12), - listen_streak=5, - ), - # User 2 has a broken listen streak of 5 - override should take effect - ChallengeListenStreak( - user_id=2, - last_listen_date=datetime.now() - timedelta(hours=50), - listen_streak=5, - ), - # User 3 has a new listen streak of 3 - ChallengeListenStreak( - user_id=3, - last_listen_date=datetime.now() - timedelta(hours=12), - listen_streak=3, - ), - # User 4 has an ongoing listen streak of 9 - ChallengeListenStreak( - user_id=4, - last_listen_date=datetime.now() - timedelta(hours=12), - listen_streak=9, - ), - # User 5 has an ongoing listen streak of 3 - ChallengeListenStreak( - user_id=5, - last_listen_date=datetime.now() - timedelta(hours=12), - listen_streak=3, - ), - # User 6 has a broken listen streak of 3 - override should take effect - ChallengeListenStreak( - user_id=6, - last_listen_date=datetime.now() - timedelta(days=3), - listen_streak=2, - ), - ] - - # Wipe any existing challenges in the DB from running migrations, etc - session.query(Challenge).delete() - session.commit() - session.add_all(blocks) - session.commit() - session.add_all(users) - session.add_all(challenges) - session.commit() - session.add_all(user_challenges) - session.commit() - session.add_all(listen_streak_challenges) - session.commit() - - redis_conn = get_redis() - bus = ChallengeEventBus(redis_conn) - bus.register_listener(DEFAULT_EVENT, listen_streak_endless_challenge_manager) - return bus - - -def test_get_challenges_with_no_override_step_count(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_listen_streak_challenge(session) - - challenges = get_challenges(1, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["current_step_count"] == 5 - - -def test_get_challenges_with_override_step_count(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_listen_streak_challenge(session) - - challenges = get_challenges(2, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["current_step_count"] == 0 - - -def test_listen_streak_endless_challenge(app): - with app.app_context(): - db = get_db() - with db.scoped_session() as session: - bus = setup_listen_streak_challenge(session) - - challenges = get_challenges(3, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["is_complete"] == False - assert challenges[0]["amount"] == "1" - assert challenges[0]["current_step_count"] == 3 - - challenges = get_challenges(4, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["is_complete"] == True - assert challenges[0]["amount"] == "1" - assert challenges[0]["current_step_count"] == 9 - - challenges = get_challenges(5, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["is_complete"] == True - assert challenges[0]["current_step_count"] == 3 - assert challenges[0]["amount"] == "1" - - challenges = get_challenges(6, False, session, bus) - assert len(challenges) == 1 - assert challenges[0]["challenge_id"] == "e" - assert challenges[0]["is_complete"] == True - assert challenges[0]["current_step_count"] == 0 - assert challenges[0]["amount"] == "1" diff --git a/packages/discovery-provider/integration_tests/queries/test_get_collections_library.py b/packages/discovery-provider/integration_tests/queries/test_get_collections_library.py deleted file mode 100644 index e3947a0201c..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_collections_library.py +++ /dev/null @@ -1,323 +0,0 @@ -from datetime import datetime - -from integration_tests.utils import populate_mock_db -from src.queries.get_collection_library import ( - CollectionType, - GetCollectionLibraryArgs, - LibraryFilterType, - _get_collection_library, -) -from src.queries.get_track_library import SortDirection, SortMethod -from src.utils.db_session import get_db - - -def populate_entries(db): - test_entities = { - "tracks": [ - { - "track_id": 1, - "title": "track 1", - "owner_id": 1, - "release_date": datetime(2019, 6, 17), - "created_at": datetime(2019, 6, 17), - }, - { - "track_id": 2, - "title": "track 2", - "owner_id": 2, - "release_date": datetime(2019, 6, 15), - "created_at": datetime(2019, 6, 15), - }, - { - "track_id": 3, - "title": "track 3", - "owner_id": 2, - "release_date": datetime(2019, 6, 15), - "created_at": datetime(2019, 6, 15), - }, - { - "track_id": 4, - "title": "track 4", - "owner_id": 1, - "is_unlisted": True, - "release_date": datetime(2019, 6, 15), - "created_at": datetime(2019, 6, 15), - }, - { - "track_id": 5, - "title": "track 5", - "owner_id": 2, - "is_unlisted": True, - "release_date": datetime(2019, 6, 15), - "created_at": datetime(2019, 6, 15), - }, - ], - "playlists": [ - # playlists ----- - { - "playlist_id": 1, - "playlist_owner_id": 1, - "is_album": False, - "created_at": datetime(2019, 6, 17), - "playlist_contents": {"track_ids": [{"track": 1}]}, - }, - # user 1 hidden playlist - { - "playlist_id": 5, - "playlist_owner_id": 1, - "is_album": False, - "is_private": True, - "created_at": datetime(2019, 6, 18), - "playlist_name": "asdf", - "playlist_contents": {"track_ids": [{"track": 1}, {"track": 2}]}, - }, - # user 1 playlist of all hidden tracks - { - "playlist_id": 6, - "playlist_owner_id": 1, - "is_album": False, - "created_at": datetime(2019, 6, 19), - "playlist_name": "asdf", - "playlist_contents": {"track_ids": [{"track": 4}]}, - }, - # user 2 hidden playlist - { - "playlist_id": 7, - "playlist_owner_id": 2, - "is_album": False, - "is_private": True, - "created_at": datetime(2019, 6, 17), - "playlist_name": "asdf", - "playlist_contents": {"track_ids": [{"track": 1}, {"track": 2}]}, - }, - # user 2 playlist of all hidden tracks - { - "playlist_id": 8, - "playlist_owner_id": 2, - "is_album": False, - "created_at": datetime(2019, 6, 18), - "playlist_name": "asdf", - "playlist_contents": {"track_ids": [{"track": 5}]}, - }, - # reposted - { - "playlist_id": 3, - "playlist_owner_id": 2, - "is_album": False, - "created_at": datetime(2019, 6, 15), - "playlist_contents": {"track_ids": [{"track": 3}]}, - }, - # albums ------- - { - "playlist_id": 2, - "playlist_owner_id": 1, - "is_album": True, - "created_at": datetime(2019, 6, 17), - "playlist_name": "asdf", - "playlist_contents": {"track_ids": [{"track": 1}]}, - }, - # saved - { - "playlist_id": 4, - "playlist_owner_id": 2, - "is_album": True, - "created_at": datetime(2019, 6, 15), - "playlist_name": "xyz", - "playlist_contents": {"track_ids": [{"track": 2}]}, - }, - ], - "users": [ - {"user_id": 1}, - {"user_id": 2, "name": "testhandle123"}, - {"user_id": 3}, - {"user_id": 4}, - ], - } - test_social_feature_entities = { - "reposts": [ - { - "repost_item_id": 3, - "repost_type": "playlist", - "user_id": 1, - "created_at": datetime(2019, 6, 16), - } - ], - "saves": [ - { - "save_item_id": 4, - "save_type": "album", - "user_id": 1, - "created_at": datetime(2019, 6, 16), - }, - # saved hidden playlist - { - "save_item_id": 7, - "save_type": "playlist", - "user_id": 1, - "created_at": datetime(2019, 6, 17), - }, - # saved playlist of all hidden tracks - { - "save_item_id": 8, - "save_type": "playlist", - "user_id": 1, - "created_at": datetime(2019, 6, 18), - }, - ], - } - populate_mock_db(db, test_entities) - populate_mock_db(db, test_social_feature_entities) - - -def with_collection_library_setup(test_fn): - def wrapper(app): - with app.app_context(): - db = get_db() - populate_entries(db) - with db.scoped_session() as session: - test_fn(session) - - return wrapper - - -def assert_correct_collection(collections, index, playlist_id, note): - assert collections[index]["playlist_id"] == playlist_id, note - - -@with_collection_library_setup -def test_distinguishes_albums_vs_favorites(session): - args = GetCollectionLibraryArgs( - user_id=1, - limit=10, - offset=0, - filter_type=LibraryFilterType.all, - collection_type=CollectionType.album, - ) - - # Default to descending sort on item_created_at - res = _get_collection_library(args, session) - assert len(res) == 2 - assert_correct_collection(res, 0, 2, "should get only albums") - assert_correct_collection(res, 1, 4, "should get only albums") - - args["collection_type"] = CollectionType.playlist - res = _get_collection_library(args, session) - assert len(res) == 4 - assert_correct_collection( - res, - 0, - 6, - "should return playlists of all hidden tracks belonging to current user", - ) - assert_correct_collection( - res, 1, 5, "should return hidden playlists belonging to user" - ) - assert_correct_collection(res, 2, 1, "should get only playlists") - assert_correct_collection(res, 3, 3, "should get only playlists") - - -@with_collection_library_setup -def test_filter_methods(session): - args = GetCollectionLibraryArgs( - user_id=1, - limit=10, - offset=0, - filter_type=LibraryFilterType.favorite, - collection_type=CollectionType.album, - ) - - # Default to descending sort on item_created_at - # Favorite should also return 'own' items - res = _get_collection_library(args, session) - assert len(res) == 2 - assert_correct_collection(res, 0, 2, "should get only saved albums") - assert_correct_collection(res, 1, 4, "should get only saved albums") - - args["filter_type"] = LibraryFilterType.repost - args["collection_type"] = CollectionType.playlist - - res = _get_collection_library(args, session) - assert len(res) == 1 - assert_correct_collection(res, 0, 3, "should get only reposted albums") - - -# Distinguishes albums and playlists -@with_collection_library_setup -def test_sort_orders(session): - # Test by sort method: added_date, reposts, saves - - # Test added date - args = GetCollectionLibraryArgs( - user_id=1, - limit=10, - offset=0, - filter_type=LibraryFilterType.all, - collection_type=CollectionType.album, - sort_direction=SortDirection.desc, - sort_method=SortMethod.added_date, - ) - - res = _get_collection_library(args, session) - assert len(res) == 2 - assert_correct_collection(res, 0, 2, "sort by desc item_created_at") - assert_correct_collection(res, 1, 4, "sort by desc item_created_at") - - # Test sort by fav count - args["sort_method"] = SortMethod.saves - res = _get_collection_library(args, session) - assert len(res) == 2 - assert_correct_collection(res, 0, 4, "sort by desc save") - assert_correct_collection(res, 1, 2, "sort by desc save") - - # Test sort by repost count - args["sort_method"] = SortMethod.reposts - args["collection_type"] = CollectionType.playlist - res = _get_collection_library(args, session) - assert len(res) == 4 - assert_correct_collection(res, 0, 3, "sort by desc repost") - assert_correct_collection(res, 1, 1, "sort by desc repost") - - -@with_collection_library_setup -def test_query(session): - # query by self playlist tile - args = GetCollectionLibraryArgs( - user_id=1, - limit=10, - offset=0, - filter_type=LibraryFilterType.all, - collection_type=CollectionType.album, - sort_direction=SortDirection.desc, - sort_method=SortMethod.added_date, - query="asdf", - ) - - res = _get_collection_library(args, session) - assert len(res) == 1 - assert_correct_collection(res, 0, 2, "matches track title query for self playlist") - - # By saved playlist title - args = GetCollectionLibraryArgs( - user_id=1, - limit=10, - offset=0, - filter_type=LibraryFilterType.all, - collection_type=CollectionType.album, - sort_direction=SortDirection.desc, - sort_method=SortMethod.added_date, - query="xyz", - ) - - res = _get_collection_library(args, session) - assert len(res) == 1 - assert_correct_collection( - res, 0, 4, "matches track title query for saved playlist " - ) - - # query by user handle - args["sort_method"] = SortMethod.reposts - args["collection_type"] = CollectionType.playlist - args["query"] = "testhandle123" - res = _get_collection_library(args, session) - assert len(res) == 1 - assert_correct_collection(res, 0, 3, "matches user handle") diff --git a/packages/discovery-provider/integration_tests/queries/test_get_genre_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_genre_metrics.py deleted file mode 100644 index a703048e8a0..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_genre_metrics.py +++ /dev/null @@ -1,99 +0,0 @@ -from datetime import datetime, timedelta - -from src.models.indexing.block import Block -from src.models.tracks.track import Track -from src.queries.get_genre_metrics import _get_genre_metrics -from src.utils.db_session import get_db - - -def populate_mock_db(db, test_tracks, date): - """Helper function to populate thee mock DB with plays""" - with db.scoped_session() as session: - for i, track_meta in enumerate(test_tracks): - blockhash = hex(i) - block = Block( - blockhash=blockhash, - number=i, - parenthash="0x01", - is_current=(i == 0), - ) - track = Track( - blockhash=hex(i), - blocknumber=i, - track_id=i, - is_current=track_meta.get("is_current", True), - is_delete=track_meta.get("is_delete", False), - owner_id=300, - route_id="", - track_segments=[], - genre=track_meta.get("genre", ""), - updated_at=track_meta.get("updated_at", date), - created_at=track_meta.get("created_at", date), - is_unlisted=track_meta.get("is_unlisted", False), - ) - # add block and then flush before - # adding track, bc track.blocknumber foreign key - # references block - session.add(block) - session.flush() - session.add(track) - - -def test_get_genre_metrics(app): - """Tests that genre metrics can be queried""" - with app.app_context(): - db = get_db() - - test_tracks = [{"genre": "Electronic"}, {"genre": "Pop"}, {"genre": "Electronic"}] - - date = datetime(2020, 10, 4, 10, 35, 0) - before_date = date + timedelta(hours=-1) - populate_mock_db(db, test_tracks, date) - - args = {"start_time": before_date} - - with db.scoped_session() as session: - metrics = _get_genre_metrics(session, args) - - assert metrics[0]["name"] == "Electronic" - assert metrics[0]["count"] == 2 - assert metrics[1]["name"] == "Pop" - assert metrics[1]["count"] == 1 - - -def test_get_genre_metrics_for_month(app): - """Tests that genre metrics can be queried over a large time range""" - date = datetime(2020, 10, 4, 10, 35, 0) - long_before_date = date + timedelta(days=-12) - before_date = date + timedelta(days=-1) - - with app.app_context(): - db = get_db() - - test_tracks = [ - {"genre": "Electronic", "created_at": date}, - {"genre": "Pop", "created_at": date}, - {"genre": "Electronic", "created_at": date}, - {"genre": "Electronic", "created_at": before_date}, - ] - populate_mock_db(db, test_tracks, date) - - args = {"start_time": before_date} - - with db.scoped_session() as session: - metrics = _get_genre_metrics(session, args) - - assert metrics[0]["name"] == "Electronic" - assert metrics[0]["count"] == 2 - assert metrics[1]["name"] == "Pop" - assert metrics[1]["count"] == 1 - - args2 = {"start_time": long_before_date} - - with db.scoped_session() as session: - metrics = _get_genre_metrics(session, args2) - - assert metrics[0]["name"] == "Electronic" - assert metrics[0]["count"] == 3 - assert metrics[1]["name"] == "Pop" - assert metrics[1]["count"] == 1 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_historical_app_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_historical_app_metrics.py deleted file mode 100644 index 3351b5fb6cd..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_historical_app_metrics.py +++ /dev/null @@ -1,127 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_app_name_metrics import ( - AggregateDailyAppNameMetric, -) -from src.models.metrics.aggregate_monthly_app_name_metrics import ( - AggregateMonthlyAppNameMetric, -) -from src.queries.get_app_name_metrics import _get_historical_app_metrics -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) -day_before_yesterday = today - timedelta(days=2) -thirty_days_ago = today - timedelta(days=30) - - -def test_get_historical_app_metrics(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyAppNameMetric( - application_name="top-app", - count=1, - timestamp=thirty_days_ago - timedelta(days=1), - ), - AggregateDailyAppNameMetric( - application_name="top-app", count=2, timestamp=thirty_days_ago - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=1, timestamp=thirty_days_ago - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=3, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=4, timestamp=today - ), - AggregateMonthlyAppNameMetric( - application_name="top-app", - count=2, - timestamp=today - timedelta(days=367), - ), - AggregateMonthlyAppNameMetric( - application_name="best-app", - count=4, - timestamp=today - timedelta(days=100), - ), - AggregateMonthlyAppNameMetric( - application_name="other-app", - count=5, - timestamp=today - timedelta(days=100), - ), - AggregateMonthlyAppNameMetric( - application_name="top-app", count=6, timestamp=today - ), - ] - ) - - aggregate_metrics = _get_historical_app_metrics(session, 0) - daily_aggregate_metrics = aggregate_metrics["daily"] - monthly_aggregate_metrics = aggregate_metrics["monthly"] - - assert len(daily_aggregate_metrics.items()) == 2 - assert daily_aggregate_metrics[str(thirty_days_ago)]["top-app"] == 2 - assert daily_aggregate_metrics[str(thirty_days_ago)]["best-app"] == 1 - assert daily_aggregate_metrics[str(yesterday)]["best-app"] == 3 - - assert len(daily_aggregate_metrics.items()) == 2 - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=367))]["top-app"] == 2 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))]["best-app"] == 4 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))]["other-app"] - == 5 - ) - - -def test_get_historical_app_metrics_with_min_count(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyAppNameMetric( - application_name="best-app", count=4, timestamp=yesterday - ), - AggregateDailyAppNameMetric( - application_name="best-app", count=3, timestamp=day_before_yesterday - ), - AggregateDailyAppNameMetric( - application_name="top-app", count=6, timestamp=day_before_yesterday - ), - AggregateMonthlyAppNameMetric( - application_name="other-app", - count=3, - timestamp=today - timedelta(days=100), - ), - AggregateMonthlyAppNameMetric( - application_name="top-app", - count=6, - timestamp=today - timedelta(days=100), - ), - ] - ) - - # Min count of 4 - aggregate_metrics = _get_historical_app_metrics(session, 4) - daily_aggregate_metrics = aggregate_metrics["daily"] - monthly_aggregate_metrics = aggregate_metrics["monthly"] - - assert len(daily_aggregate_metrics.items()) == 2 - assert daily_aggregate_metrics[str(yesterday)]["best-app"] == 4 - assert daily_aggregate_metrics[str(day_before_yesterday)]["top-app"] == 6 - - assert len(monthly_aggregate_metrics.items()) == 1 - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))]["top-app"] == 6 - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_historical_route_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_historical_route_metrics.py deleted file mode 100644 index de8993bca8b..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_historical_route_metrics.py +++ /dev/null @@ -1,110 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.queries.get_historical_route_metrics import _get_historical_route_metrics -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) -thirty_days_ago = today - timedelta(days=30) - - -def test_get_historical_route_metrics(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, - summed_count=2, - timestamp=thirty_days_ago - timedelta(days=1), - ), - AggregateDailyUniqueUsersMetrics( - count=2, summed_count=3, timestamp=thirty_days_ago - ), - AggregateDailyUniqueUsersMetrics( - count=3, summed_count=4, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, summed_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, timestamp=thirty_days_ago - timedelta(days=1) - ), - AggregateDailyTotalUsersMetrics(count=4, timestamp=thirty_days_ago), - AggregateDailyTotalUsersMetrics(count=6, timestamp=yesterday), - AggregateDailyTotalUsersMetrics(count=8, timestamp=today), - AggregateMonthlyUniqueUsersMetric( - count=1, summed_count=2, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyUniqueUsersMetric( - count=2, summed_count=3, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyUniqueUsersMetric( - count=3, summed_count=4, timestamp=today - ), - AggregateMonthlyTotalUsersMetric( - count=2, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyTotalUsersMetric( - count=4, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyTotalUsersMetric(count=6, timestamp=today), - ] - ) - - aggregate_metrics = _get_historical_route_metrics(session) - daily_aggregate_metrics = aggregate_metrics["daily"] - monthly_aggregate_metrics = aggregate_metrics["monthly"] - - assert len(daily_aggregate_metrics.items()) == 2 - assert daily_aggregate_metrics[str(thirty_days_ago)]["unique_count"] == 2 - assert daily_aggregate_metrics[str(thirty_days_ago)]["summed_unique_count"] == 3 - assert daily_aggregate_metrics[str(thirty_days_ago)]["total_count"] == 4 - assert daily_aggregate_metrics[str(yesterday)]["unique_count"] == 3 - assert daily_aggregate_metrics[str(yesterday)]["summed_unique_count"] == 4 - assert daily_aggregate_metrics[str(yesterday)]["total_count"] == 6 - - assert len(monthly_aggregate_metrics.items()) == 2 - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=367))]["unique_count"] - == 1 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=367))][ - "summed_unique_count" - ] - == 2 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=367))]["total_count"] - == 2 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))]["unique_count"] - == 2 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))][ - "summed_unique_count" - ] - == 3 - ) - assert ( - monthly_aggregate_metrics[str(today - timedelta(days=100))]["total_count"] - == 4 - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_personal_route_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_personal_route_metrics.py deleted file mode 100644 index d9221a693bb..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_personal_route_metrics.py +++ /dev/null @@ -1,223 +0,0 @@ -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.queries.get_personal_route_metrics import _get_personal_route_metrics -from src.utils.db_session import get_db - -limit = 2 -today = date.today() -yesterday = today - timedelta(days=1) - - -def test_get_personal_route_metrics_week(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, personal_count=2, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=2, personal_count=1, timestamp=yesterday - timedelta(days=1) - ), - AggregateDailyUniqueUsersMetrics( - count=3, personal_count=3, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, personal_count=2, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, personal_count=1, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics( - count=4, personal_count=4, timestamp=yesterday - timedelta(days=1) - ), - AggregateDailyTotalUsersMetrics( - count=6, personal_count=5, timestamp=yesterday - ), - AggregateDailyTotalUsersMetrics( - count=8, personal_count=8, timestamp=today - ), - ] - ) - - personal_metrics = _get_personal_route_metrics(session, "week", "day") - - assert len(personal_metrics) == 2 - assert personal_metrics[0]["unique_count"] == 1 - assert personal_metrics[0]["total_count"] == 4 - assert personal_metrics[1]["unique_count"] == 3 - assert personal_metrics[1]["total_count"] == 5 - - -def test_get_personal_route_metrics_month_daily_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, personal_count=1, timestamp=today - timedelta(days=31) - ), - AggregateDailyUniqueUsersMetrics( - count=2, personal_count=1, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=3, personal_count=2, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, personal_count=5, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, personal_count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyTotalUsersMetrics( - count=4, personal_count=3, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics( - count=6, personal_count=6, timestamp=yesterday - ), - AggregateDailyTotalUsersMetrics( - count=8, personal_count=5, timestamp=today - ), - ] - ) - - personal_metrics = _get_personal_route_metrics(session, "month", "day") - - assert len(personal_metrics) == 2 - assert personal_metrics[0]["unique_count"] == 1 - assert personal_metrics[0]["total_count"] == 3 - assert personal_metrics[1]["unique_count"] == 2 - assert personal_metrics[1]["total_count"] == 6 - - -def test_get_personal_route_metrics_month_weekly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=2, personal_count=1, timestamp=today - timedelta(days=31) - ), - AggregateDailyUniqueUsersMetrics( - count=2, personal_count=2, timestamp=today - timedelta(days=8) - ), - AggregateDailyUniqueUsersMetrics( - count=3, personal_count=3, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=4, personal_count=4, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, personal_count=2, timestamp=today - timedelta(days=31) - ), - AggregateDailyTotalUsersMetrics( - count=4, personal_count=3, timestamp=today - timedelta(days=8) - ), - AggregateDailyTotalUsersMetrics( - count=6, personal_count=6, timestamp=yesterday - ), - AggregateDailyTotalUsersMetrics( - count=8, personal_count=7, timestamp=today - ), - ] - ) - - personal_metrics = _get_personal_route_metrics(session, "month", "week") - - assert len(personal_metrics) == 2 - assert personal_metrics[0]["unique_count"] == 2 - assert personal_metrics[0]["total_count"] == 3 - assert personal_metrics[1]["unique_count"] == 3 - assert personal_metrics[1]["total_count"] == 6 - - -def test_get_personal_route_metrics_all_time_monthly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateMonthlyUniqueUsersMetric( - count=1, personal_count=1, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyUniqueUsersMetric( - count=2, personal_count=2, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyUniqueUsersMetric( - count=3, personal_count=3, timestamp=today - ), - AggregateMonthlyTotalUsersMetric( - count=2, personal_count=2, timestamp=today - timedelta(days=367) - ), - AggregateMonthlyTotalUsersMetric( - count=4, personal_count=3, timestamp=today - timedelta(days=100) - ), - AggregateMonthlyTotalUsersMetric( - count=6, personal_count=4, timestamp=today - ), - ] - ) - - personal_metrics = _get_personal_route_metrics(session, "all_time", "month") - - assert len(personal_metrics) == 2 - assert personal_metrics[0]["unique_count"] == 1 - assert personal_metrics[0]["total_count"] == 2 - assert personal_metrics[1]["unique_count"] == 2 - assert personal_metrics[1]["total_count"] == 3 - - -def test_get_personal_route_metrics_all_time_weekly_bucket(app): - with app.app_context(): - db_mock = get_db() - - with db_mock.scoped_session() as session: - session.bulk_save_objects( - [ - AggregateDailyUniqueUsersMetrics( - count=1, personal_count=1, timestamp=today - timedelta(days=367) - ), - AggregateDailyUniqueUsersMetrics( - count=2, personal_count=2, timestamp=yesterday - ), - AggregateDailyUniqueUsersMetrics( - count=3, personal_count=3, timestamp=today - ), - AggregateDailyTotalUsersMetrics( - count=2, personal_count=1, timestamp=today - timedelta(days=367) - ), - AggregateDailyTotalUsersMetrics( - count=4, personal_count=3, timestamp=yesterday - ), - AggregateDailyTotalUsersMetrics( - count=6, personal_count=6, timestamp=today - ), - ] - ) - - personal_metrics = _get_personal_route_metrics(session, "all_time", "week") - - assert len(personal_metrics) == 2 - assert personal_metrics[0]["unique_count"] == 1 - assert personal_metrics[0]["total_count"] == 1 - assert personal_metrics[1]["unique_count"] == 2 - assert personal_metrics[1]["total_count"] == 3 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_plays_metrics.py b/packages/discovery-provider/integration_tests/queries/test_get_plays_metrics.py deleted file mode 100644 index d0cd8c3646b..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_plays_metrics.py +++ /dev/null @@ -1,158 +0,0 @@ -import time -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_plays_metrics import GetPlayMetricsArgs, _get_plays_metrics -from src.tasks.index_hourly_play_counts import _index_hourly_play_counts -from src.utils.db_session import get_db - -DAYS_IN_A_YEAR = 365 - - -def format_date(date): - return int(time.mktime(date.timetuple())) - - -def test_get_plays_metrics(app): - """Tests that plays metrics can be queried""" - - with app.app_context(): - db = get_db() - - date = datetime(2020, 10, 4).replace(minute=0, second=0, microsecond=0) - test_entities = { - "tracks": [ - {"track_id": 1, "title": "track 1"}, - {"track_id": 2, "title": "track 2"}, - {"track_id": 3, "title": "track 3"}, - ], - "plays": [ - {"item_id": 1, "created_at": date + timedelta(hours=-1)}, - {"item_id": 1, "created_at": date + timedelta(hours=-1)}, - {"item_id": 1, "created_at": date + timedelta(days=-2)}, - {"item_id": 2, "created_at": date + timedelta(hours=-1)}, - {"item_id": 2, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - ], - } - - populate_mock_db(db, test_entities) - - args = GetPlayMetricsArgs( - limit=10, - start_time=date + timedelta(days=-3), - bucket_size="hour", - ) - - with db.scoped_session() as session: - _index_hourly_play_counts(session) - metrics = _get_plays_metrics(session, args) - - assert len(metrics) == 2 - assert metrics[0]["timestamp"] == format_date(date + timedelta(hours=-1)) - assert metrics[0]["count"] == 3 - assert metrics[1]["timestamp"] == format_date(date + timedelta(days=-2)) - assert metrics[1]["count"] == 5 - - -def test_get_plays_metrics_with_weekly_buckets(app): - """Tests that plays metrics can be queried with weekly buckets""" - - with app.app_context(): - db = get_db() - - # A Thursday - date = datetime(2020, 10, 1).replace(minute=0, second=0, microsecond=0) - test_entities = { - "tracks": [ - {"track_id": 1, "title": "track 1"}, - {"track_id": 2, "title": "track 2"}, - {"track_id": 3, "title": "track 3"}, - ], - "plays": [ - {"item_id": 1, "created_at": date + timedelta(hours=-1)}, - {"item_id": 1, "created_at": date + timedelta(hours=-1)}, - {"item_id": 1, "created_at": date + timedelta(days=-2)}, - {"item_id": 2, "created_at": date + timedelta(hours=-1)}, - {"item_id": 2, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - {"item_id": 3, "created_at": date + timedelta(days=-2)}, - ], - } - - populate_mock_db(db, test_entities) - - start_time = date + timedelta(days=-3) - args = GetPlayMetricsArgs( - limit=10, - start_time=date + timedelta(days=-3), - bucket_size="week", - ) - - with db.scoped_session() as session: - _index_hourly_play_counts(session) - metrics = _get_plays_metrics(session, args) - - assert len(metrics) == 1 - assert metrics[0]["count"] == 8 - assert metrics[0]["timestamp"] == format_date(start_time) - - -def test_get_plays_metrics_with_yearly_buckets(app): - """Tests that plays metrics can be queried""" - - with app.app_context(): - db = get_db() - - date = datetime(2020, 10, 4).replace(minute=0, second=0, microsecond=0) - test_entities = { - "tracks": [ - {"track_id": 1, "title": "track 1"}, - {"track_id": 2, "title": "track 2"}, - {"track_id": 3, "title": "track 3"}, - ], - "plays": [ - {"item_id": 1, "created_at": date + timedelta(days=-3 * DAYS_IN_A_YEAR)}, - {"item_id": 1, "created_at": date + timedelta(days=-3 * DAYS_IN_A_YEAR)}, - {"item_id": 3, "created_at": date + timedelta(days=-3 * DAYS_IN_A_YEAR)}, - {"item_id": 1, "created_at": date + timedelta(days=-2 * DAYS_IN_A_YEAR)}, - {"item_id": 2, "created_at": date + timedelta(days=-2 * DAYS_IN_A_YEAR)}, - {"item_id": 3, "created_at": date + timedelta(days=-1 * DAYS_IN_A_YEAR)}, - {"item_id": 3, "created_at": date + timedelta(days=-1)}, - {"item_id": 3, "created_at": date + timedelta(weeks=-1)}, - ], - } - - populate_mock_db(db, test_entities) - - start_time = date + timedelta( - days=-3 * DAYS_IN_A_YEAR - 1 - ) # -1 extra day to be inclusive - args = GetPlayMetricsArgs( - limit=10, - start_time=start_time, - bucket_size="year", - ) - - with db.scoped_session() as session: - _index_hourly_play_counts(session) - metrics = _get_plays_metrics(session, args) - - assert len(metrics) == 4 - assert metrics[0]["count"] == 2 - assert metrics[0]["timestamp"] == format_date(date.replace(day=1, month=1)) - assert metrics[1]["count"] == 1 - assert metrics[1]["timestamp"] == format_date( - (date + timedelta(days=-1 * DAYS_IN_A_YEAR)).replace(day=1, month=1) - ) - assert metrics[2]["count"] == 2 - assert metrics[2]["timestamp"] == format_date( - (date + timedelta(days=-2 * DAYS_IN_A_YEAR)).replace(day=1, month=1) - ) - assert metrics[3]["count"] == 3 - assert metrics[3]["timestamp"] == format_date( - (date + timedelta(days=-3 * DAYS_IN_A_YEAR)).replace(day=1, month=1) - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_prev_track_entries.py b/packages/discovery-provider/integration_tests/queries/test_get_prev_track_entries.py deleted file mode 100644 index b2a7dc18f58..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_prev_track_entries.py +++ /dev/null @@ -1,132 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.models.tracks.track import Track -from src.queries.get_prev_track_entries import get_prev_track_entries -from src.utils.db_session import get_db - - -def test_get_prev_track_entries(app): - """Tests that prev track entries query properly returns previous tracks""" - with app.app_context(): - db = get_db() - - test_entities = { - "tracks": [ - { - "track_id": 1, - "is_current": False, - "is_unlisted": True, - "remix_of": None, - }, # Block 0 - { - "track_id": 2, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 1 - { - "track_id": 3, - "is_current": False, - "is_unlisted": False, - "remix_of": None, - }, # Block 2 - { - "track_id": 4, - "is_current": False, - "is_unlisted": False, - "remix_of": None, - }, # Block 3 - { - "track_id": 5, - "is_current": False, - "is_unlisted": False, - "remix_of": None, - }, # Block 4 - { - "track_id": 6, - "is_current": False, - "is_unlisted": True, - "remix_of": None, - }, # Block 5 - { - "track_id": 1, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 6 - { - "track_id": 3, - "is_current": False, - "is_unlisted": False, - "remix_of": None, - }, # Block 7 - { - "track_id": 6, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 8 - { - "track_id": 4, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 9 - { - "track_id": 3, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 10 - { - "track_id": 5, - "is_current": False, - "is_unlisted": False, - "remix_of": None, - }, # Block 11 - { - "track_id": 5, - "is_current": True, - "is_unlisted": False, - "remix_of": None, - }, # Block 12 - ], - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - # Make sure it doesn't return tracks if none are passed in - empty_entries = get_prev_track_entries(session, []) - - assert len(empty_entries) == 0 - - # Make sure that it fetches all previous tracks - entries = [ - Track(track_id=6, blocknumber=8), - Track(track_id=6, blocknumber=8), - Track(track_id=3, blocknumber=10), - Track(track_id=1, blocknumber=6), - Track(track_id=4, blocknumber=9), - Track(track_id=5, blocknumber=12), - ] - prev_entries = get_prev_track_entries(session, entries) - - assert len(prev_entries) <= len(entries) - - for prev_entry in prev_entries: - entry = next(e for e in entries if e.track_id == prev_entry.track_id) - assert entry.track_id == prev_entry.track_id - assert entry.blocknumber > prev_entry.blocknumber - # previous track with id 3 should have a block number of 7, not 2 - if prev_entry.track_id == 3: - assert prev_entry.blocknumber == 7 - # previous track with id 5 should have a block number of 11, not 4 - if prev_entry.track_id == 5: - assert prev_entry.blocknumber == 11 - - # Make sure that it properly fetches the track before the one passed - single_entry = [Track(track_id=5, blocknumber=11)] - prev_id_5_track = get_prev_track_entries(session, single_entry)[0] - - assert prev_id_5_track.track_id == 5 - assert prev_id_5_track.blocknumber < 11 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_related_artists.py b/packages/discovery-provider/integration_tests/queries/test_get_related_artists.py deleted file mode 100644 index fd2f67bcf53..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_related_artists.py +++ /dev/null @@ -1,167 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_related_artists import get_related_artists -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def test_get_related_artists(app): - """Test getting related artists based on genre similarity""" - with app.app_context(): - db = get_db() - - test_entities = { - "tracks": [ - # User 1's tracks - mostly Electronic - { - "track_id": 1, - "owner_id": 1, - "genre": "Electronic", - }, - { - "track_id": 2, - "owner_id": 1, - "genre": "Electronic", - }, - { - "track_id": 3, - "owner_id": 1, - "genre": "Pop", - }, - # User 2's tracks - only Electronic - { - "track_id": 4, - "owner_id": 2, - "genre": "Electronic", - }, - { - "track_id": 5, - "owner_id": 2, - "genre": "Electronic", - }, - # User 3's tracks - only Pop - { - "track_id": 6, - "owner_id": 3, - "genre": "Pop", - }, - { - "track_id": 7, - "owner_id": 3, - "genre": "Pop", - }, - # User 4's tracks - mix of genres - { - "track_id": 8, - "owner_id": 4, - "genre": "Electronic", - }, - { - "track_id": 9, - "owner_id": 4, - "genre": "Pop", - }, - ], - "users": [ - {"user_id": 1, "handle": "user1"}, - {"user_id": 2, "handle": "user2"}, - {"user_id": 3, "handle": "user3"}, - {"user_id": 4, "handle": "user4"}, - ], - "follows": [ - { - "follower_user_id": 1, - "followee_user_id": 2, - }, # User 1 follows User 2 - ], - "aggregate_user": [ - {"user_id": 1, "follower_count": 100, "dominant_genre": "Electronic"}, - { - "user_id": 2, - "follower_count": 50, - "dominant_genre": "Electronic", - }, # Should be recommended (similar genre, lower follower count) - { - "user_id": 3, - "follower_count": 400, - "dominant_genre": "Pop", - }, # Should not be recommended (too many followers) - { - "user_id": 4, - "follower_count": 75, - "dominant_genre": "Electronic", - }, # Should be recommended - ], - } - - populate_mock_db(db, test_entities) - - # Test basic related artists functionality - related = get_related_artists(1, None) - assert len(related) == 2 # Should get users 2 and 4 - assert ( - related[0]["user_id"] == 4 - ) # User 2 should be first (same genre_rank but higher follower count) - assert ( - related[1]["user_id"] == 2 - ) # User 4 should be second (same genre_rank but lower follower count) - - # Test with filter_followed=True and current_user_id - related = get_related_artists(1, 1, filter_followed=True) - assert len(related) == 1 # Should just be 4 since user 1 follows user 2 - user_ids = [user["user_id"] for user in related] - assert 4 in user_ids - - # Test pagination - related = get_related_artists(1, None, limit=1) - assert len(related) == 1 - assert ( - related[0]["user_id"] == 4 - ) # First related artist should be user 2 (same genre_rank but lower follower count) - - related = get_related_artists(1, None, limit=1, offset=1) - assert len(related) == 1 - assert ( - related[0]["user_id"] == 2 - ) # Second related artist should be user 4 (same genre_rank but higher follower count) - - -def test_get_related_artists_edge_cases(app): - """Test edge cases for getting related artists""" - with app.app_context(): - db = get_db() - - test_entities = { - "tracks": [ - # User with no tracks (user 1) - # User with deleted/unlisted tracks (user 2) - {"track_id": 1, "owner_id": 2, "genre": "Electronic"}, - {"track_id": 2, "owner_id": 2, "genre": "Electronic"}, - # User with valid tracks (user 3) - {"track_id": 3, "owner_id": 3, "genre": "Electronic"}, - ], - "users": [ - {"user_id": 1, "handle": "user1"}, - {"user_id": 2, "handle": "user2"}, - {"user_id": 3, "handle": "user3"}, - ], - "aggregate_user": [ - {"user_id": 1, "follower_count": 100, "dominant_genre": "Electronic"}, - {"user_id": 2, "follower_count": 50, "dominant_genre": "Electronic"}, - {"user_id": 3, "follower_count": 75, "dominant_genre": "Electronic"}, - ], - } - - populate_mock_db(db, test_entities) - - # Test user with no tracks - related = get_related_artists(1, None) - assert ( - len(related) == 0 - ) # Should get no users since user 1 has no valid tracks to determine genre from - - # Test with non-existent user - related = get_related_artists(999, None) - assert len(related) == 0 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_repost_feed_for_user.py b/packages/discovery-provider/integration_tests/queries/test_get_repost_feed_for_user.py deleted file mode 100644 index 03f6aa46e26..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_repost_feed_for_user.py +++ /dev/null @@ -1,119 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_repost_feed_for_user import _get_repost_feed_for_user -from src.utils.db_session import get_db - - -def test_get_repost_feed_for_user(app): - """Tests that a repost feed for a user can be queried""" - with app.app_context(): - db = get_db() - - test_entities = { - "reposts": [ - # Note these reposts are in chronological order in addition - # so the repost feed should pull them "backwards" for reverse chronological - # sort order. - {"user_id": 1, "repost_item_id": 5, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 2, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 3, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 1, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 4, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 4, "repost_type": "playlist"}, - {"user_id": 1, "repost_item_id": 8, "repost_type": "album"}, - {"user_id": 1, "repost_item_id": 6, "repost_type": "track"}, - ], - "tracks": [ - {"track_id": 1, "title": "track 1"}, - {"track_id": 2, "title": "track 2"}, - {"track_id": 3, "title": "track 3"}, - {"track_id": 4, "title": "track 4"}, - {"track_id": 5, "title": "track 5"}, - {"track_id": 6, "title": "track 6"}, - {"track_id": 7, "title": "track 7"}, - {"track_id": 8, "title": "track 8"}, - ], - "playlists": [ - {"playlist_id": 1, "playlist_name": "playlist 1"}, - {"playlist_id": 2, "playlist_name": "playlist 2"}, - {"playlist_id": 3, "playlist_name": "playlist 3"}, - {"playlist_id": 4, "playlist_name": "playlist 4"}, - {"playlist_id": 5, "playlist_name": "playlist 5"}, - {"playlist_id": 6, "playlist_name": "playlist 6"}, - {"playlist_id": 7, "playlist_name": "playlist 7"}, - {"playlist_id": 8, "playlist_name": "album 8"}, - ], - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - repost_feed = _get_repost_feed_for_user(session, 1, {"limit": 10, "offset": 0}) - - assert repost_feed[0]["title"] == "track 6" - assert repost_feed[1]["playlist_name"] == "album 8" - assert repost_feed[2]["playlist_name"] == "playlist 4" - assert repost_feed[3]["title"] == "track 4" - assert repost_feed[4]["title"] == "track 1" - assert repost_feed[5]["title"] == "track 3" - assert repost_feed[6]["title"] == "track 2" - assert repost_feed[7]["title"] == "track 5" - - -def test_get_repost_feed_for_user_limit_bounds(app): - """ - Tests that a repost feed for a user can be queried and respect a limit - with deleted tracks. - """ - with app.app_context(): - db = get_db() - - test_entities = { - "reposts": [ - # Note these reposts are in chronological order in addition - # so the repost feed should pull them "backwards" for reverse chronological - # sort order. - {"user_id": 1, "repost_item_id": 5, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 2, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 3, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 1, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 4, "repost_type": "track"}, - {"user_id": 1, "repost_item_id": 4, "repost_type": "playlist"}, - {"user_id": 1, "repost_item_id": 8, "repost_type": "album"}, - {"user_id": 1, "repost_item_id": 6, "repost_type": "track"}, - ], - "tracks": [ - {"track_id": 1, "title": "track 1", "is_delete": True}, - {"track_id": 2, "title": "track 2"}, - {"track_id": 3, "title": "track 3"}, - {"track_id": 4, "title": "track 4"}, - {"track_id": 5, "title": "track 5"}, - {"track_id": 6, "title": "track 6"}, - {"track_id": 7, "title": "track 7"}, - {"track_id": 8, "title": "track 8"}, - ], - "playlists": [ - {"playlist_id": 1, "playlist_name": "playlist 1"}, - {"playlist_id": 2, "playlist_name": "playlist 2"}, - {"playlist_id": 3, "playlist_name": "playlist 3"}, - {"playlist_id": 4, "playlist_name": "playlist 4"}, - {"playlist_id": 5, "playlist_name": "playlist 5"}, - {"playlist_id": 6, "playlist_name": "playlist 6"}, - {"playlist_id": 7, "playlist_name": "playlist 7"}, - {"playlist_id": 8, "playlist_name": "album 8"}, - ], - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - repost_feed = _get_repost_feed_for_user(session, 1, {"limit": 5, "offset": 0}) - - # Query for 5 reposts. The problem is the 5th one was deleted, so - # we only return 4 here. This is broken. - # TODO fix me. - assert repost_feed[0]["title"] == "track 6" - assert repost_feed[1]["playlist_name"] == "album 8" - assert repost_feed[2]["playlist_name"] == "playlist 4" - assert repost_feed[3]["title"] == "track 4" - # Should skip track 1 because it is deleted - assert repost_feed[4]["title"] == "track 3" diff --git a/packages/discovery-provider/integration_tests/queries/test_get_tips.py b/packages/discovery-provider/integration_tests/queries/test_get_tips.py deleted file mode 100644 index a0afbc7e5bd..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_tips.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging - -import pytest - -from integration_tests.utils import populate_mock_db -from src.queries.get_tips import GetTipsArgs, get_tips -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -@pytest.fixture -def test_entities(): - return { - "users": [ - {"user_id": 1, "handle": "user1"}, - {"user_id": 2, "handle": "user2"}, - {"user_id": 3, "handle": "DontShowMyTips"}, - ], - "follows": [ - {"follower_user_id": 1, "followee_user_id": 2}, - {"follower_user_id": 1, "followee_user_id": 3}, - ], - "user_tips": [ - { - "slot": 0, - "signature": "abcdefg", - "sender_user_id": 1, - "receiver_user_id": 2, - "amount": 100000000, - }, - { - "slot": 1, - "signature": "abcdefg", - "sender_user_id": 1, - "receiver_user_id": 3, - "amount": 100000000, - }, - ], - "aggregate_user_tips": [ - {"sender_user_id": 1, "receiver_user_id": 2, "amount": 100000000}, - {"sender_user_id": 1, "receiver_user_id": 3, "amount": 100000000}, - ], - } - - -def test_exclude_receivers_from_query(app, test_entities): - # Add pagination variables to only retrieve first tip result - with app.test_request_context("?limit=1&offset=0"): - db = get_db() - populate_mock_db(db, test_entities) - with db.scoped_session(): - # Test first without filtering, should get the most recent tip - tips_unfiltered = get_tips( - GetTipsArgs( - user_id=1, - current_user_follows=True, - unique_by="receiver", - ) - ) - - assert len(tips_unfiltered) == 1 - tip = tips_unfiltered[0] - assert tip["sender"]["user_id"] == 1 - assert tip["receiver"]["user_id"] == 3 - - # Filter out recipient with most recent tip, should get the next - # most recent tip - tips = get_tips( - GetTipsArgs( - user_id=1, - current_user_follows=True, - unique_by="receiver", - exclude_recipients=[3], - ) - ) - - assert len(tips) == 1 - tip = tips[0] - assert tip["sender"]["user_id"] == 1 - assert tip["receiver"]["user_id"] == 2 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_top_genre_users.py b/packages/discovery-provider/integration_tests/queries/test_get_top_genre_users.py deleted file mode 100644 index e8de56e46f5..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_top_genre_users.py +++ /dev/null @@ -1,46 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_top_genre_users import _get_top_genre_users -from src.tasks.update_aggregates import _update_aggregates -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def test_get_top_genre_users(app): - with app.app_context(): - db = get_db() - - entities = { - "tracks": [ - {"track_id": 1, "owner_id": 1, "genre": "Electronic"}, - {"track_id": 2, "owner_id": 2, "genre": "Electronic"}, - {"track_id": 3, "owner_id": 2, "genre": "Pop"}, - {"track_id": 4, "owner_id": 2, "genre": "Pop"}, - {"track_id": 5, "owner_id": 3, "genre": "Pop"}, - {"track_id": 6, "owner_id": 4, "genre": "Electronic"}, - ], - "users": [{"user_id": 1}, {"user_id": 2}, {"user_id": 3}, {"user_id": 4}], - "follows": [ - {"follower_user_id": 1, "followee_user_id": 2}, - {"follower_user_id": 2, "followee_user_id": 1}, - {"follower_user_id": 2, "followee_user_id": 3}, - {"follower_user_id": 3, "followee_user_id": 2}, - {"follower_user_id": 3, "followee_user_id": 4}, - ], - } - - populate_mock_db(db, entities) - with db.scoped_session() as session: - _update_aggregates(session) - - with db.scoped_session() as session: - users = _get_top_genre_users(session, {"genre": "Pop"}) - assert users[0]["user_id"] == 2 - assert users[1]["user_id"] == 3 - - users = _get_top_genre_users(session, {"genre": "Electronic"}) - # Tie break goes to user 1 over user 4 - assert users[0]["user_id"] == 1 - assert users[1]["user_id"] == 4 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_top_user_track_tags.py b/packages/discovery-provider/integration_tests/queries/test_get_top_user_track_tags.py deleted file mode 100644 index 7272f931d85..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_top_user_track_tags.py +++ /dev/null @@ -1,34 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_top_user_track_tags import _get_top_user_track_tags -from src.utils.db_session import get_db - - -def test_get_top_user_track_tags(app): - """Tests that top tags for users can be queried""" - with app.app_context(): - db = get_db() - - test_entities = { - "tracks": [ - {"tags": ""}, - {}, - {"tags": "pop,rock,electric"}, - {"tags": "pop,rock"}, - {"tags": "funk,pop"}, - ] - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - session.execute("REFRESH MATERIALIZED VIEW tag_track_user") - user_1_tags = _get_top_user_track_tags(session, {"user_id": 1}) - user_2_tags = _get_top_user_track_tags(session, {"user_id": 2}) - - assert len(user_1_tags) == 4 - assert user_1_tags[0] == "pop" - assert user_1_tags[1] == "rock" - assert "electric" in user_1_tags - assert "funk" in user_1_tags - - assert not user_2_tags diff --git a/packages/discovery-provider/integration_tests/queries/test_get_total_plays.py b/packages/discovery-provider/integration_tests/queries/test_get_total_plays.py deleted file mode 100644 index 73746318863..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_total_plays.py +++ /dev/null @@ -1,41 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_total_plays import _get_total_plays -from src.utils.db_session import get_db - - -def test_get_total_plays(app): - """Tests that total plays can be queried""" - with app.app_context(): - db = get_db() - - # Set up test data - test_entities = { - "aggregate_plays": [ - {"count": 100}, - {"count": 200}, - {"count": 300}, - ] - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - total = _get_total_plays(session) - - assert total == 600 - - -def test_get_total_plays_empty(app): - """Tests that total plays returns 0 when no plays exist""" - with app.app_context(): - db = get_db() - - # Set up empty test data - test_entities = {"aggregate_plays": []} - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - total = _get_total_plays(session) - - assert total == 0 diff --git a/packages/discovery-provider/integration_tests/queries/test_get_tracks.py b/packages/discovery-provider/integration_tests/queries/test_get_tracks.py deleted file mode 100644 index 9ac592c5c6a..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_tracks.py +++ /dev/null @@ -1,575 +0,0 @@ -from datetime import datetime - -from integration_tests.utils import populate_mock_db -from src.queries.get_remixable_tracks import get_remixable_tracks -from src.queries.get_remixes_of import get_remixes_of -from src.queries.get_tracks import _get_tracks -from src.queries.query_helpers import SortDirection, SortMethod -from src.utils.db_session import get_db - - -def populate_tracks(db): - test_entities = { - "tracks": [ - { - "track_id": 1, - "title": "track 1", - "owner_id": 1287289, - "release_date": datetime(2019, 12, 20), - "created_at": datetime(2018, 5, 17), - }, - { - "track_id": 2, - "title": "track 2", - "owner_id": 1287289, - "created_at": datetime(2018, 5, 18), - }, - { - "track_id": 3, - "title": "track 3", - "owner_id": 1287289, - "release_date": datetime(2019, 12, 18), - "created_at": datetime(2020, 5, 17), - "ai_attribution_user_id": 1287289, - }, - { - "track_id": 4, - "title": "track 4", - "owner_id": 1287289, - "release_date": None, - "created_at": datetime(2018, 5, 19), - }, - { - "track_id": 5, - "title": "track 5", - "owner_id": 1287289, - "release_date": None, - "created_at": datetime(2018, 5, 20), - }, - { - "track_id": 6, - "title": "track 6", - "owner_id": 4, - "release_date": datetime(2019, 12, 18), - "created_at": datetime(2020, 5, 17), - }, - { - "track_id": 7, - "title": "track 7", - "owner_id": 4, - "release_date": None, - "created_at": datetime(2018, 5, 19), - }, - { - "track_id": 8, - "title": "track 8", - "owner_id": 4, - "release_date": None, - "created_at": datetime(2018, 5, 20), - }, - { - "track_id": 9, - "title": "track 9", - "owner_id": 4, - "release_date": datetime(2019, 12, 25), - "created_at": datetime(2018, 5, 20), - "is_unlisted": True, - }, - { - "track_id": 10, - "title": "track 10", - "owner_id": 4, - "release_date": datetime(2019, 12, 25), - "created_at": datetime(2018, 5, 21), - "is_delete": True, - }, - { - "track_id": 11, - "title": "track 11", - "owner_id": 1287289, - "release_date": datetime(2019, 12, 19), - "created_at": datetime(2018, 5, 17), - "is_unlisted": True, - }, - { - "track_id": 12, - "title": "track 12", - "owner_id": 5, - "release_date": datetime(2020, 6, 19), - "created_at": datetime(2018, 5, 21), - "ai_attribution_user_id": 1287289, - }, - { - "track_id": 13, - "title": "track 13", - "owner_id": 5, - "release_date": datetime(2022, 10, 7), - "created_at": datetime(2018, 5, 17), - }, - { - "track_id": 14, - "title": "track 14", - "owner_id": 5, - "release_date": datetime(2019, 12, 25), - "created_at": datetime(2020, 5, 17), - }, - { - "track_id": 15, - "title": "track 15", - "owner_id": 1287289, - "release_date": None, - "created_at": datetime(2017, 5, 19), - }, - { - "track_id": 16, - "title": "track 16", - "owner_id": 1287289, - "release_date": datetime(2017, 5, 19), - "created_at": datetime(2017, 5, 19), - }, - ], - "track_routes": [ - {"slug": "track-1", "owner_id": 1287289}, - {"slug": "track-2", "owner_id": 1287289}, - { - "slug": "different-track", - "owner_id": 4, - "track_id": 6, - }, - { - "slug": "track-1", - "owner_id": 4, - "track_id": 7, - }, - { - "slug": "track-2", - "owner_id": 4, - "track_id": 8, - }, - { - "slug": "hidden-track", - "owner_id": 4, - "track_id": 9, - }, - ], - "users": [ - {"user_id": 1287289, "handle": "some-test-user"}, - {"user_id": 4, "wallet": "0xuser4wallet", "handle": "some-other-user"}, - { - "user_id": 5, - "handle": "test-user-5", - "artist_pick_track_id": 12, - "allow_ai_attribution": True, - }, - ], - "grants": [ - { - "user_id": 1287289, - "grantee_address": "0xuser4wallet", - "is_approved": True, - "is_revoked": False, - }, - ], - } - - populate_mock_db(db, test_entities) - - -def test_get_tracks_by_date(app): - """Test getting tracks ordering by date""" - - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - tracks = _get_tracks( - session, {"user_id": 1287289, "offset": 0, "limit": 10, "sort": "date"} - ) - - assert len(tracks) == 7 - assert tracks[0]["track_id"] == 1 - assert tracks[1]["track_id"] == 3 - assert tracks[2]["track_id"] == 5 - assert tracks[3]["track_id"] == 4 - assert tracks[4]["track_id"] == 2 - - # tracks created on the same day, with one missing 'release_date` - # should fall back to sorting by id - assert tracks[5]["track_id"] == 15 - assert tracks[6]["track_id"] == 16 - - assert tracks[0]["permalink"] == "/some-test-user/track-1" - assert tracks[4]["permalink"] == "/some-test-user/track-2" - - -def test_get_tracks_with_query(app): - """Test getting tracks with a query""" - - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - tracks = _get_tracks( - session, {"user_id": 1287289, "offset": 0, "limit": 10, "query": "track 5"} - ) - - assert len(tracks) == 1 - assert tracks[0]["track_id"] == 5 - - -def test_get_tracks_by_date_authed(app): - """ - Test getting tracks ordering by date with an authed user. - This test should produce unlisted tracks. - """ - - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - # test as authed user matching owner - tracks = _get_tracks( - session, - { - "user_id": 1287289, - "authed_user_id": 1287289, - "offset": 0, - "limit": 10, - "sort": "date", - }, - ) - - assert len(tracks) == 8 - assert tracks[0]["track_id"] == 1 - assert tracks[1]["track_id"] == 11 - assert tracks[2]["track_id"] == 3 - assert tracks[3]["track_id"] == 5 - assert tracks[4]["track_id"] == 4 - assert tracks[5]["track_id"] == 2 - - # test as authed user managing owner - tracks = _get_tracks( - session, - { - "user_id": 1287289, - "current_user_id": 1287289, - "authed_user_id": 4, - "offset": 0, - "limit": 10, - "sort": "date", - }, - ) - - assert len(tracks) == 8 - assert tracks[0]["track_id"] == 1 - assert tracks[1]["track_id"] == 11 - assert tracks[2]["track_id"] == 3 - assert tracks[3]["track_id"] == 5 - assert tracks[4]["track_id"] == 4 - assert tracks[5]["track_id"] == 2 - - -def test_get_tracks_with_pinned_track(app): - """ - Test getting tracks for a user with a pinned track. The - pinned track should be the first result, with all other tracks - sorted according to the sort parameter. - """ - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - tracks = _get_tracks( - session, {"user_id": 5, "offset": 0, "limit": 10, "sort": "date"} - ) - - assert len(tracks) == 3 - assert tracks[0]["track_id"] == 12 - assert tracks[1]["track_id"] == 13 - assert tracks[2]["track_id"] == 14 - - -def test_get_tracks_with_pinned_track_and_sort_method(app): - """ - Test getting tracks for a user with a pinned track. All tracks - should be sorted according the sort method. The pinned track is - not necessarily the first result. - """ - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - tracks = _get_tracks( - session, - { - "user_id": 5, - "offset": 0, - "limit": 10, - "sort_method": SortMethod.release_date, - "sort_direction": SortDirection.desc, - }, - ) - - assert len(tracks) == 3 - assert tracks[0]["track_id"] == 13 - assert tracks[1]["track_id"] == 12 - assert tracks[2]["track_id"] == 14 - - -def test_get_track_by_route(app): - """Test getting track by user handle and slug for route resolution""" - with app.app_context(): - db = get_db() - - populate_tracks(db) - - with db.scoped_session() as session: - tracks = _get_tracks( - session, - { - "routes": [{"owner_id": 1287289, "slug": "track-1"}], - "offset": 0, - "limit": 10, - }, - ) - - assert len(tracks) == 1, "track-1 is found for some-test-user" - assert tracks[0]["owner_id"] == 1287289 - assert tracks[0]["permalink"] == "/some-test-user/track-1" - - tracks = _get_tracks( - session, - { - "routes": [{"owner_id": 4, "slug": "track-1"}], - "offset": 0, - "limit": 10, - }, - ) - assert len(tracks) == 1, "track-1 is still found for some-other-user" - assert tracks[0]["owner_id"] == 4 - assert tracks[0]["permalink"] == "/some-other-user/track-1" - - # Get an unlisted track - tracks = _get_tracks( - session, - { - "routes": [{"owner_id": 4, "slug": "hidden-track"}], - "offset": 0, - "limit": 10, - }, - ) - assert len(tracks) == 1 - assert tracks[0]["owner_id"] == 4 - assert tracks[0]["permalink"] == "/some-other-user/hidden-track" - - # Make sure unlisted tracks are hidden without slug - tracks = _get_tracks( - session, - {"user_id": 4, "id": [9], "offset": 0, "limit": 10}, - ) - assert len(tracks) == 0 - - -def test_get_remixable_tracks(app): - with app.app_context(): - db = get_db() - - populate_tracks(db) - populate_mock_db( - db, - { - "remixes": [ - {"parent_track_id": 9, "child_track_id": 1}, - {"parent_track_id": 8, "child_track_id": 1}, - ], - "stems": [ - {"parent_track_id": 7, "child_track_id": 1}, - {"parent_track_id": 6, "child_track_id": 1}, - # Verify that tracks with deleted stems are not returned - {"parent_track_id": 5, "child_track_id": 10}, - ], - "saves": [{"user_id": 4, "save_item_id": 1}], - "reposts": [{"user_id": 4, "repost_item_id": 1}], - }, - ) - - tracks = get_remixable_tracks({"with_users": True}) - assert len(tracks) == 2 - assert tracks[0]["user"] - - -def test_get_remixes_of(app): - with app.app_context(): - db = get_db() - - populate_tracks(db) - populate_mock_db( - db, - { - "remixes": [ - {"parent_track_id": 1, "child_track_id": 2}, - {"parent_track_id": 1, "child_track_id": 3}, - {"parent_track_id": 1, "child_track_id": 4}, - ], - "aggregate_plays": [ - {"play_item_id": 2, "count": 100}, - {"play_item_id": 4, "count": 50}, - {"play_item_id": 3, "count": 2}, - ], - "saves": [ - {"user_id": 1287289, "save_item_id": 2}, - ], - "reposts": [ - {"user_id": 1287289, "repost_item_id": 4}, - ], - "events": [ - { - "user_id": 1287289, - "entity_id": 1, - "created_at": datetime(2018, 5, 19), - "end_date": datetime(2018, 5, 20), - } - ], - }, - ) - - tracks = get_remixes_of({"track_id": 1, "sort_method": "plays"})["tracks"] - assert len(tracks) == 3 - assert tracks[0]["track_id"] == 2 - assert tracks[1]["track_id"] == 4 - assert tracks[2]["track_id"] == 3 - - tracks = get_remixes_of({"track_id": 1, "only_cosigns": True})["tracks"] - assert len(tracks) == 2 - assert tracks[0]["track_id"] == 4 - assert tracks[1]["track_id"] == 2 - - tracks = get_remixes_of({"track_id": 1, "only_contest_entries": True})["tracks"] - assert len(tracks) == 1 - assert tracks[0]["track_id"] == 4 - - -def test_get_remixes_of_with_multiple_events(app): - """Test that get_remixes_of doesn't return duplicate tracks when there are multiple active events for the same track. - Note that this should not happen, but we've encountered this bug before so worth adding a test case. - """ - with app.app_context(): - db = get_db() - - populate_tracks(db) - populate_mock_db( - db, - { - "tracks": [ - { - "track_id": 2, - "title": "remix track 2", - "owner_id": 1287289, - "created_at": datetime( - 2018, 5, 10 - ), # Outside contest period (before 2018-05-15) - "remix_of": {"tracks": [{"parent_track_id": 1}]}, - }, - { - "track_id": 3, - "title": "remix track 3", - "owner_id": 1287289, - "created_at": datetime(2020, 5, 17), - "remix_of": {"tracks": [{"parent_track_id": 1}]}, - }, - { - "track_id": 4, - "title": "remix track 4", - "owner_id": 1287289, - "created_at": datetime( - 2018, 5, 20 - ), # Within the contest period 2018-05-15 to 2018-05-30 - "remix_of": {"tracks": [{"parent_track_id": 1}]}, - }, - ], - "remixes": [ - {"parent_track_id": 1, "child_track_id": 2}, - {"parent_track_id": 1, "child_track_id": 3}, - {"parent_track_id": 1, "child_track_id": 4}, - ], - "aggregate_plays": [ - {"play_item_id": 2, "count": 100}, - {"play_item_id": 4, "count": 50}, - {"play_item_id": 3, "count": 2}, - ], - "saves": [ - {"user_id": 1287289, "save_item_id": 2}, - ], - "reposts": [ - {"user_id": 1287289, "repost_item_id": 4}, - ], - # Multiple active events for the same track - this would cause duplicates without DISTINCT ON fix - "events": [ - { - "user_id": 1287289, - "entity_id": 1, - "event_type": "remix_contest", - "is_deleted": False, - "created_at": datetime(2018, 5, 15), - "end_date": datetime(2018, 5, 30), - }, - { - "user_id": 1287289, - "entity_id": 1, - "event_type": "remix_contest", - "is_deleted": False, - "created_at": datetime(2018, 5, 15), - "end_date": datetime(2018, 5, 30), - }, - { - "user_id": 1287289, - "entity_id": 1, - "event_type": "remix_contest", - "is_deleted": False, - "created_at": datetime(2018, 5, 15), - "end_date": datetime(2018, 5, 30), - }, - ], - }, - ) - - # Test basic query - should return 3 unique tracks, not 9 (3 tracks × 3 events) - tracks = get_remixes_of({"track_id": 1, "sort_method": "plays"})["tracks"] - assert ( - len(tracks) == 3 - ), f"Expected 3 unique tracks, got {len(tracks)} - possible duplication from multiple events" - assert tracks[0]["track_id"] == 2 - assert tracks[1]["track_id"] == 4 - assert tracks[2]["track_id"] == 3 - - # Test cosigns filter - should return 2 unique tracks, not 6 - tracks = get_remixes_of({"track_id": 1, "only_cosigns": True})["tracks"] - assert ( - len(tracks) == 2 - ), f"Expected 2 unique tracks with cosigns, got {len(tracks)} - possible duplication from multiple events" - assert tracks[0]["track_id"] == 4 - assert tracks[1]["track_id"] == 2 - - # Test contest entries filter - should return 1 unique track that falls within ANY contest period - # Track 4 (created 2018-05-19) falls within the second contest period (2018-05-19 to 2018-05-22) - tracks = get_remixes_of({"track_id": 1, "only_contest_entries": True})["tracks"] - assert ( - len(tracks) == 1 - ), f"Expected 1 track matching contest criteria, got {len(tracks)} - possible duplication from multiple events" - assert tracks[0]["track_id"] == 4 - - # Test that count in response is also correct (not inflated by duplicates) - response = get_remixes_of({"track_id": 1, "sort_method": "plays"}) - assert ( - response["count"] == 3 - ), f"Expected count of 3, got {response['count']} - count may be inflated by duplicates" diff --git a/packages/discovery-provider/integration_tests/queries/test_get_usdc_purchases.py b/packages/discovery-provider/integration_tests/queries/test_get_usdc_purchases.py deleted file mode 100644 index 8728ae7dbf2..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_usdc_purchases.py +++ /dev/null @@ -1,233 +0,0 @@ -from datetime import datetime - -from integration_tests.utils import populate_mock_db -from src.models.users.usdc_purchase import PurchaseType -from src.queries.get_usdc_purchases import get_usdc_purchases -from src.queries.query_helpers import PurchaseSortMethod, SortDirection -from src.utils.db_session import get_db - -test_entities = { - "users": [ - {"user_id": 10, "name": "a"}, - {"user_id": 20, "name": "c"}, - {"user_id": 30, "name": "b"}, - ], - "tracks": [ - { - "track_id": 1, - "title": "a", - "owner_id": 10, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 100, - "splits": {"some_user_bank": 1000000}, - } - }, - }, - { - "track_id": 2, - "title": "f", - "owner_id": 10, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 299, - "splits": {"some_user_bank": 2990000}, - } - }, - }, - { - "track_id": 3, - "title": "c", - "owner_id": 10, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 199, - "splits": {"some_user_bank": 1990000}, - } - }, - }, - { - "track_id": 4, - "title": "b", - "owner_id": 10, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 350, - "splits": {"some_user_bank": 3500000}, - } - }, - }, - { - "track_id": 5, - "title": "d", - "owner_id": 20, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 100, - "splits": {"some_user_bank": 1000000}, - } - }, - }, - { - "track_id": 6, - "title": "zzz", - "owner_id": 20, - "is_stream_gated": True, - "stream_conditions": { - "usdc_purchase": { - "price": 100, - "splits": {"some_user_bank": 1000000}, - } - }, - }, - ], - "usdc_purchases": [ - { - "slot": 5, - "buyer_user_id": 20, - "seller_user_id": 10, - "amount": 350, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 4, - "created_at": datetime(2023, 8, 11), - "splits": [{"user_id": 10, "amount": 3500000, "percentage": 100}], - }, - { - "slot": 4, - "buyer_user_id": 20, - "seller_user_id": 10, - "amount": 199, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 3, - "created_at": datetime(2023, 8, 10), - "splits": [{"user_id": 10, "amount": 1990000, "percentage": 100}], - }, - { - "slot": 8, - "buyer_user_id": 30, - "seller_user_id": 20, - "amount": 100, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 5, - "created_at": datetime(2023, 8, 14), - "splits": [{"user_id": 20, "amount": 1000000, "percentage": 100}], - }, - { - "slot": 7, - "buyer_user_id": 30, - "seller_user_id": 10, - "amount": 199, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 3, - "created_at": datetime(2023, 8, 13, 12), - "splits": [{"user_id": 10, "amount": 1990000, "percentage": 100}], - }, - { - "slot": 6, - "buyer_user_id": 30, - "seller_user_id": 10, - "amount": 299, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 2, - "created_at": datetime(2023, 8, 12), - "splits": [{"user_id": 10, "amount": 2990000, "percentage": 100}], - }, - { - "slot": 9, - "buyer_user_id": 20, - "seller_user_id": 10, - "amount": 299, - "extra_amount": 0, - "content_type": PurchaseType.track, - "content_id": 2, - "created_at": datetime(2023, 8, 13), - "splits": [{"user_id": 10, "amount": 2990000, "percentage": 100}], - }, - ], -} - - -def test_get_purchases(app): - with app.app_context(): - db = get_db() - populate_mock_db(db, test_entities) - sales = get_usdc_purchases( - {"seller_user_id": 10, "sort_method": PurchaseSortMethod.date} - ) - assert len(sales) == 5 - assert sales[0]["content_id"] == 3 - assert sales[1]["content_id"] == 2 - assert not any(purchase["content_id"] == 1 for purchase in sales) - assert not any(purchase["content_id"] == 5 for purchase in sales) - assert all(purchase["seller_user_id"] == 10 for purchase in sales) - - purchases = get_usdc_purchases( - {"buyer_user_id": 20, "sort_method": PurchaseSortMethod.date} - ) - assert len(purchases) == 3 - assert all(purchase["buyer_user_id"] == 20 for purchase in purchases) - - specific_sale = get_usdc_purchases( - { - "content_ids": [3], - "content_type": PurchaseType.track, - "sort_method": PurchaseSortMethod.date, - } - ) - assert len(specific_sale) == 2 - assert specific_sale[0]["content_id"] == 3 - assert specific_sale[1]["content_id"] == 3 - - purchases_by_title_asc = get_usdc_purchases( - { - "content_type": PurchaseType.track, - "sort_method": PurchaseSortMethod.content_title, - "sort_direction": SortDirection.asc, - } - ) - - by_title_mapped = [ - next( - track["title"] - for track in test_entities["tracks"] - if track["track_id"] == purchase["content_id"] - ) - for purchase in purchases_by_title_asc - ] - assert by_title_mapped == sorted(by_title_mapped) - - purchases_by_artist_desc = get_usdc_purchases( - { - "content_type": PurchaseType.track, - "sort_method": PurchaseSortMethod.artist_name, - "sort_direction": SortDirection.desc, - } - ) - - by_artist_owner_ids = [ - next( - track["owner_id"] - for track in test_entities["tracks"] - if track["track_id"] == purchase["content_id"] - ) - for purchase in purchases_by_artist_desc - ] - by_artist_mapped = [ - next( - user["name"] - for user in test_entities["users"] - if user["user_id"] == owner_id - ) - for owner_id in by_artist_owner_ids - ] - assert by_artist_mapped == list(reversed(sorted(by_artist_mapped))) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_usdc_transactions_history.py b/packages/discovery-provider/integration_tests/queries/test_get_usdc_transactions_history.py deleted file mode 100644 index 5c9fdf47060..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_usdc_transactions_history.py +++ /dev/null @@ -1,146 +0,0 @@ -import pytest - -from integration_tests.utils import populate_mock_db -from src.models.users.usdc_transactions_history import USDCTransactionMethod -from src.queries.get_usdc_transactions_history import ( - GetUSDCTransactionsArgs, - GetUSDCTransactionsCountArgs, - get_usdc_transactions_history, - get_usdc_transactions_history_count, -) -from src.queries.query_helpers import SortDirection, TransactionSortMethod -from src.utils.db_session import get_db - -user1_id = 10 -user1_name = "user1" -user1_eth_address = "0xe66402f9a6714a874a539fb1689b870dd271dfb2" -user1_user_bank_address = "7G1angvMtUZLFMyrMDGj7bxsduB4bjLD7VXRR7N4FXqe" - -external_recipient_address = "3nmLmEzwBBjERiV9UU1a4JXzESwDjrKZdSjP1KG4M9Mc" -internal_recipient_address = "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W" - - -@pytest.fixture -def test_entities(): - return { - "users": [ - {"user_id": 1, "handle": "user1", "wallet": user1_eth_address}, - ], - "usdc_user_bank_accounts": [ - { - "signature": "unused1", - "ethereum_address": user1_eth_address, - "bank_account": user1_user_bank_address, - } - ], - "usdc_transactions_history": [ - { - "user_bank": user1_user_bank_address, - "signature": "manualTransferSignature", - "transaction_type": "transfer", - "method": "send", - "change": -1000000, - "balance": 1000000, - "tx_metadata": external_recipient_address, - }, - { - "user_bank": user1_user_bank_address, - "signature": "firstPrepareWithdrawalSignature", - "transaction_type": "prepare_withdrawal", - "method": "send", - "change": -1000000, - "balance": 0, - "tx_metadata": internal_recipient_address, - }, - { - "user_bank": user1_user_bank_address, - "signature": "recoverWithdrawalSignature", - "transaction_type": "recover_withdrawal", - "method": "send", - "change": 1000000, - "balance": 1000000, - "tx_metadata": internal_recipient_address, - }, - { - "user_bank": user1_user_bank_address, - "signature": "secondPrepareWithdrawalSignature", - "transaction_type": "prepare_withdrawal", - "method": "send", - "change": -1000000, - "balance": 0, - "tx_metadata": internal_recipient_address, - }, - { - "user_bank": user1_user_bank_address, - "signature": "withdrawalSignature", - "transaction_type": "withdrawal", - "method": "send", - "change": -1000000, - "balance": 0, - "tx_metadata": external_recipient_address, - }, - ], - } - - -def test_get_usdc_transactions_history_default_excludes_system_transactions( - app, test_entities -): - with app.test_request_context( - # Request context and args are required for passing - # pagination info into paginate_query inside the query function - data={"limit": 10, "offset": 0}, - ): - db = get_db() - populate_mock_db(db, test_entities) - transactions = get_usdc_transactions_history( - GetUSDCTransactionsArgs( - user_id=1, - transaction_method=USDCTransactionMethod.send, - sort_method=TransactionSortMethod.date, - sort_direction=SortDirection.asc, - ) - ) - count = get_usdc_transactions_history_count( - GetUSDCTransactionsCountArgs( - user_id=1, - transaction_method=USDCTransactionMethod.send, - ) - ) - assert len(transactions) == 2 - assert count == 2 - assert transactions[0]["signature"] == "manualTransferSignature" - assert transactions[1]["signature"] == "withdrawalSignature" - - -def test_get_usdc_transactions_including_system_transactions(app, test_entities): - with app.test_request_context( - # Request context and args are required for passing - # pagination info into paginate_query inside the query function - data={"limit": 10, "offset": 0}, - ): - db = get_db() - populate_mock_db(db, test_entities) - transactions = get_usdc_transactions_history( - GetUSDCTransactionsArgs( - user_id=1, - transaction_method=USDCTransactionMethod.send, - sort_method=TransactionSortMethod.date, - sort_direction=SortDirection.asc, - include_system_transactions=True, - ) - ) - count = get_usdc_transactions_history_count( - GetUSDCTransactionsCountArgs( - user_id=1, - transaction_method=USDCTransactionMethod.send, - include_system_transactions=True, - ) - ) - assert len(transactions) == 5 - assert count == 5 - assert transactions[0]["signature"] == "manualTransferSignature" - assert transactions[1]["signature"] == "firstPrepareWithdrawalSignature" - assert transactions[2]["signature"] == "recoverWithdrawalSignature" - assert transactions[3]["signature"] == "secondPrepareWithdrawalSignature" - assert transactions[4]["signature"] == "withdrawalSignature" diff --git a/packages/discovery-provider/integration_tests/queries/test_get_user_listen_counts_monthly.py b/packages/discovery-provider/integration_tests/queries/test_get_user_listen_counts_monthly.py deleted file mode 100644 index 8f6303f7af3..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_user_listen_counts_monthly.py +++ /dev/null @@ -1,73 +0,0 @@ -from datetime import datetime - -from integration_tests.utils import populate_mock_db -from src.queries.get_user_listen_counts_monthly import ( - GetUserListenCountsMonthlyArgs, - get_user_listen_counts_monthly, -) -from src.utils.db_session import get_db - -test_entities = { - "aggregate_monthly_plays": [ - { - "play_item_id": 1, - "timestamp": "2022-01-01", - "count": 7, - }, - { - "play_item_id": 2, - "timestamp": "2022-01-01", - "count": 2, - }, - { - "play_item_id": 1, - "timestamp": "2021-12-31", - "count": 7, - }, - { - "play_item_id": 1, - "timestamp": "2023-01-01", - "count": 2, - }, - { - "play_item_id": 4, - "timestamp": "2022-02-01", - "count": 10, - }, - ], - "tracks": [ - {"track_id": 1, "title": "track 1", "owner_id": 1}, - {"track_id": 4, "title": "track 1", "owner_id": 1}, - {"track_id": 2, "title": "track 2", "owner_id": 2}, - ], - "users": [ - {"user_id": 1, "handle": "user-1"}, - {"user_id": 2, "handle": "user-2"}, - ], -} - - -def test_get_user_listen_counts_monthly_query(app): - """Tests happy path of getting user listen counts""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session(): - user_listen_counts_monthly = get_user_listen_counts_monthly( - GetUserListenCountsMonthlyArgs( - user_id=1, - start_time="2022-01-01", - end_time="2023-01-01", - ), - ) - assert len(user_listen_counts_monthly) == 2 - # User 1 only owns track ids 1 and 4 - for listen_count in user_listen_counts_monthly: - listen_count_timestamp = datetime.combine( - listen_count["timestamp"], datetime.min.time() - ) - assert listen_count["play_item_id"] in [1, 4] - assert listen_count_timestamp >= datetime.strptime("2022-01-01", "%Y-%m-%d") - assert listen_count_timestamp < datetime.strptime("2023-01-01", "%Y-%m-%d") diff --git a/packages/discovery-provider/integration_tests/queries/test_get_user_listening_history.py b/packages/discovery-provider/integration_tests/queries/test_get_user_listening_history.py deleted file mode 100644 index 796b0b103f5..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_user_listening_history.py +++ /dev/null @@ -1,428 +0,0 @@ -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries import response_name_constants -from src.queries.get_top_listeners_for_track import _get_top_listeners_for_track -from src.queries.get_user_listening_history import ( - GetUserListeningHistoryArgs, - _get_user_listening_history, -) -from src.queries.query_helpers import SortDirection, SortMethod -from src.tasks.user_listening_history.index_user_listening_history import ( - _index_user_listening_history, -) -from src.utils.db_session import get_db - -TIMESTAMP = datetime(2011, 1, 1) - -test_entities = { - "user_listening_history": [ - { - "user_id": 1, - "listening_history": [ - {"timestamp": str(TIMESTAMP), "track_id": 3}, - {"timestamp": str(TIMESTAMP), "track_id": 4}, - ], - } - ], - "plays": [ - {"user_id": 1, "item_id": 1, "created_at": TIMESTAMP + timedelta(minutes=1)}, - {"user_id": 1, "item_id": 1, "created_at": TIMESTAMP - timedelta(minutes=1)}, - {"user_id": 1, "item_id": 1, "created_at": TIMESTAMP - timedelta(minutes=1)}, - {"user_id": 1, "item_id": 2, "created_at": TIMESTAMP + timedelta(minutes=3)}, - { - "user_id": 1, - "item_id": 1, - "created_at": TIMESTAMP + timedelta(minutes=2), - }, # duplicate play - {"user_id": 1, "item_id": 3, "created_at": TIMESTAMP + timedelta(minutes=4)}, - {"user_id": 2, "item_id": 2, "created_at": TIMESTAMP}, - ], - "tracks": [ - {"track_id": 1, "title": "track 1", "owner_id": 1}, - {"track_id": 2, "title": "track 2", "owner_id": 2}, - {"track_id": 3, "title": "track 3", "owner_id": 3}, - {"track_id": 4, "title": "track 4", "owner_id": 3}, - ], - "users": [ - {"user_id": 1, "handle": "user-1"}, - {"user_id": 2, "handle": "user-2"}, - {"user_id": 3, "handle": "user-3"}, - ], -} - - -def test_get_user_listening_history_multiple_plays(app): - """Tests listening history from user with multiple plays""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - assert len(track_history) == 4 - assert ( - track_history[0][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[0][response_name_constants.track_id] == 3 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=4) - ) - assert ( - track_history[1][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[1][response_name_constants.track_id] == 2 - assert track_history[1][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=3) - ) - assert ( - track_history[2][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[2][response_name_constants.track_id] == 1 - assert track_history[2][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=2) - ) - - -def test_get_user_listening_history_no_plays(app): - """Tests a listening history with no plays""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=3, - limit=10, - offset=0, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - assert len(track_history) == 0 - - -def test_get_user_listening_history_single_play(app): - """Tests a listening history with a single play""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=2, - limit=10, - offset=0, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - assert len(track_history) == 1 - assert ( - track_history[0][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[0][response_name_constants.track_id] == 2 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP - ) - - -def test_get_user_listening_history_pagination(app): - """Tests a track history that's limit bounded""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=1, - offset=1, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - assert len(track_history) == 1 - assert ( - track_history[0][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[0][response_name_constants.track_id] == 2 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=3) - ) - - -def test_get_user_listening_history_with_query(app): - """Tests listening history from user with a query""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query="track 2", - sort_method=None, - sort_direction=None, - ), - ) - - # We should only get one history item back - assert len(track_history) == 1 - - assert track_history[0][response_name_constants.track_id] == 2 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=3) - ) - - -def test_get_user_listening_history_custom_sort(app): - """Tests listening history from user with multiple plays""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query=None, - sort_method=SortMethod.title, - sort_direction=SortDirection.asc, - ), - ) - - assert len(track_history) == 4 - assert ( - track_history[2][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[2][response_name_constants.track_id] == 3 - assert track_history[2][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=4) - ) - assert ( - track_history[1][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[1][response_name_constants.track_id] == 2 - assert track_history[1][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=3) - ) - assert ( - track_history[0][response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[0][response_name_constants.track_id] == 1 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=2) - ) - - -def test_get_user_listening_history_sort_by_most_listens(app): - """Tests listening history from user with multiple plays""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query=None, - sort_method=SortMethod.most_listens_by_user, - sort_direction=SortDirection.asc, - ), - ) - - assert len(track_history) == 4 - assert_track_history(track_history[0], 1, TIMESTAMP + timedelta(minutes=2)) - assert_track_history(track_history[1], 3, TIMESTAMP + timedelta(minutes=4)) - assert_track_history(track_history[2], 2, TIMESTAMP + timedelta(minutes=3)) - assert_track_history(track_history[3], 4, TIMESTAMP) - - -def assert_track_history(track_history, track_id, activity_timestamp): - assert ( - track_history[response_name_constants.user][response_name_constants.balance] - is not None - ) - assert track_history[response_name_constants.track_id] == track_id - assert track_history[response_name_constants.activity_timestamp] == str( - activity_timestamp - ) - - -def test_track_top_listeners(app): - """Tests a listening history with no plays""" - with app.app_context(): - db = get_db() - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - top_listeners = _get_top_listeners_for_track(session, {"track_id": 2}) - assert len(top_listeners) == 2 - assert top_listeners[0]["user"]["user_id"] == 1 - assert top_listeners[0]["count"] == 1 - assert top_listeners[1]["user"]["user_id"] == 2 - assert top_listeners[1]["count"] == 1 - - top_listeners = _get_top_listeners_for_track(session, {"track_id": 1}) - assert len(top_listeners) == 1 - assert top_listeners[0]["user"]["user_id"] == 1 - assert top_listeners[0]["count"] == 2 - - -def test_get_user_listening_history_filter_deleted_owners(app): - """Tests that tracks with deactivated owners are filtered out of listening history""" - with app.app_context(): - db = get_db() - - test_entities_with_deleted = { - "user_listening_history": [ - { - "user_id": 1, - "listening_history": [ - {"timestamp": str(TIMESTAMP), "track_id": 1}, - {"timestamp": str(TIMESTAMP), "track_id": 2}, - ], - } - ], - "tracks": [ - {"track_id": 1, "title": "track 1", "owner_id": 1}, - {"track_id": 2, "title": "track 2", "owner_id": 2}, - ], - "users": [ - {"user_id": 1, "handle": "user-1", "is_deactivated": False}, - { - "user_id": 2, - "handle": "user-2", - "is_deactivated": True, - }, # Deactivated user - ], - } - - populate_mock_db(db, test_entities_with_deleted) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - # Should only return track 1 since track 2's owner is deactivated - assert len(track_history) == 1 - assert track_history[0][response_name_constants.track_id] == 1 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP - ) - - -def test_get_user_listening_history_filter_deleted_tracks(app): - """Tests that deleted tracks are filtered out of listening history""" - with app.app_context(): - db = get_db() - - # Use main test_entities but modify track 1 to be deleted - modified_test_entities = test_entities.copy() - modified_test_entities["tracks"] = [ - {"track_id": 1, "title": "track 1", "owner_id": 1, "is_delete": True}, - *test_entities["tracks"][1:], - ] - - populate_mock_db(db, modified_test_entities) - - with db.scoped_session() as session: - _index_user_listening_history(session) - - track_history = _get_user_listening_history( - session, - GetUserListeningHistoryArgs( - user_id=1, - limit=10, - offset=0, - query=None, - sort_method=None, - sort_direction=None, - ), - ) - - # Should not return track 1 since it is deleted - assert len(track_history) == 3 - assert track_history[0][response_name_constants.track_id] == 3 - assert track_history[0][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=4) - ) - assert track_history[1][response_name_constants.track_id] == 2 - assert track_history[1][response_name_constants.activity_timestamp] == str( - TIMESTAMP + timedelta(minutes=3) - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_get_user_signals.py b/packages/discovery-provider/integration_tests/queries/test_get_user_signals.py deleted file mode 100644 index d806dca40c6..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_user_signals.py +++ /dev/null @@ -1,129 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_user_signals import _get_user_signals -from src.utils.db_session import get_db - - -def make_user( - user_id, - handle, - wallet, - profile_picture=None, - profile_picture_sizes=None, - cover_photo=None, - cover_photo_sizes=None, -): - return { - "user_id": user_id, - "handle": handle, - "wallet": wallet, - "profile_picture": profile_picture, - "profile_picture_sizes": profile_picture_sizes, - "cover_photo": cover_photo, - "cover_photo_sizes": cover_photo_sizes, - } - - -def make_follow(follower_user_id, followee_user_id): - return {"follower_user_id": follower_user_id, "followee_user_id": followee_user_id} - - -def test_get_user_signals(app): - with app.app_context(): - db = get_db() - - test_entities = { - "users": [ - make_user(1, "user1", "wallet1"), - make_user(2, "user2", "wallet2"), - make_user(3, "user3", "wallet3"), - make_user(4, "user4", "wallet4"), - make_user(5, "user5", "wallet5"), - make_user( - 6, - "user6", - "wallet6", - profile_picture="Qm0123456789abcdef0123456789abcdef0123456789ab", - ), - make_user( - 7, - "user7", - "wallet7", - profile_picture_sizes="Qm0123456789abcdef0123456789abcdef0123456789ab", - ), - make_user( - 8, - "user8", - "wallet8", - cover_photo="Qm0123456789abcdef0123456789abcdef0123456789ab", - ), - make_user( - 9, - "user9", - "wallet9", - cover_photo_sizes="Qm0123456789abcdef0123456789abcdef0123456789ab", - ), - make_user( - 10, - "user10", - "wallet10", - profile_picture="Qm0123456789abcdef0123456789abcdef0123456789ab", - cover_photo="Qm0123456789abcdef0123456789abcdef0123456789cd", - ), - ], - "follows": [ - make_follow(2, 1), - make_follow(3, 1), - make_follow(5, 1), - make_follow(1, 5), - make_follow(2, 6), - make_follow(3, 7), - make_follow(4, 8), - make_follow(5, 9), - make_follow(10, 4), - ], - } - - populate_mock_db(db, test_entities) - - with db.scoped_session() as session: - user_signals = _get_user_signals(session, "user1") - assert user_signals["num_followers"] == 3 - assert user_signals["num_following"] == 1 - assert user_signals["has_profile_picture"] == False - assert user_signals["has_cover_photo"] == False - assert user_signals["wallet"] == "wallet1" - - user_signals = _get_user_signals(session, "user6") - assert user_signals["num_followers"] == 1 - assert user_signals["num_following"] == 0 - assert user_signals["has_profile_picture"] == True - assert user_signals["has_cover_photo"] == False - assert user_signals["wallet"] == "wallet6" - - user_signals = _get_user_signals(session, "user7") - assert user_signals["num_followers"] == 1 - assert user_signals["num_following"] == 0 - assert user_signals["has_profile_picture"] == True - assert user_signals["has_cover_photo"] == False - assert user_signals["wallet"] == "wallet7" - - user_signals = _get_user_signals(session, "user8") - assert user_signals["num_followers"] == 1 - assert user_signals["num_following"] == 0 - assert user_signals["has_profile_picture"] == False - assert user_signals["has_cover_photo"] == True - assert user_signals["wallet"] == "wallet8" - - user_signals = _get_user_signals(session, "user9") - assert user_signals["num_followers"] == 1 - assert user_signals["num_following"] == 0 - assert user_signals["has_profile_picture"] == False - assert user_signals["has_cover_photo"] == True - assert user_signals["wallet"] == "wallet9" - - user_signals = _get_user_signals(session, "user10") - assert user_signals["num_followers"] == 0 - assert user_signals["num_following"] == 1 - assert user_signals["has_profile_picture"] == True - assert user_signals["has_cover_photo"] == True - assert user_signals["wallet"] == "wallet10" diff --git a/packages/discovery-provider/integration_tests/queries/test_get_users_account.py b/packages/discovery-provider/integration_tests/queries/test_get_users_account.py deleted file mode 100644 index d964a4fa094..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_get_users_account.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest - -from integration_tests.utils import populate_mock_db -from src import exceptions -from src.queries.get_users_account import GetAccountArgs, get_account -from src.utils.db_session import get_db - -USER_1_WALLET = "0x0000000000000000000000000000000000000001" -USER_2_WALLET = "0x0000000000000000000000000000000000000002" -USER_3_WALLET = "0x0000000000000000000000000000000000000003" - - -def populate_db(db): - test_entities = { - "users": [ - {"user_id": 1, "wallet": USER_1_WALLET, "handle": "user_1"}, - {"user_id": 2, "wallet": USER_2_WALLET, "handle": "user_2"}, - {"user_id": 3, "wallet": USER_3_WALLET, "handle": "user_3"}, - ], - "playlists": [ - { - "playlist_id": 1, - "playlist_owner_id": 1, - "playlist_name": "playlist 1", - }, - { - "playlist_id": 2, - "playlist_owner_id": 1, - "playlist_name": "album 1", - "is_album": True, - }, - { - "playlist_id": 3, - "playlist_owner_id": 1, - "playlist_name": "playlist 1", - "is_private": True, - }, - ], - "grants": [ - # Grant for user2 to manage user1 - { - "user_id": 1, - "grantee_address": USER_2_WALLET, - "is_approved": True, - "is_revoked": False, - } - ], - } - populate_mock_db(db, test_entities) - - -def test_get_account(app): - """Test getting account with a valid wallet""" - - with app.app_context(): - db = get_db() - - populate_db(db) - - account = get_account(GetAccountArgs(wallet=USER_1_WALLET, authed_user_id=1)) - user = account["user"] - playlists = account["playlists"] - - assert user["wallet"] == USER_1_WALLET - assert user["user_id"] == 1 - assert len(playlists) == 3 - - -def test_get_account_invalid_wallet_length(app): - with app.app_context(): - db = get_db() - - populate_db(db) - with pytest.raises(exceptions.ArgumentError): - get_account(GetAccountArgs(wallet="0x123", authed_user_id=1)) - - -def test_get_account_not_found(app): - """Test attempting to fetch nonexistent user account""" - - with app.app_context(): - db = get_db() - - populate_db(db) - account = get_account( - GetAccountArgs( - wallet="0x0000000000000000000000000000000000000009", authed_user_id=1 - ) - ) - assert account is None - - -def test_get_account_as_manager(app): - """Test getting account as a different user with manager access""" - - with app.app_context(): - db = get_db() - - populate_db(db) - - account = get_account(GetAccountArgs(wallet=USER_1_WALLET, authed_user_id=2)) - user = account["user"] - playlists = account["playlists"] - - assert user["wallet"] == USER_1_WALLET - assert user["user_id"] == 1 - assert len(playlists) == 3 - - -def test_get_account_forbidden(app): - """Test getting account as a different user without manager access""" - - with app.app_context(): - db = get_db() - - populate_db(db) - with pytest.raises(exceptions.PermissionError): - get_account(GetAccountArgs(wallet=USER_1_WALLET, authed_user_id=3)) diff --git a/packages/discovery-provider/integration_tests/queries/test_mutual_follows.py b/packages/discovery-provider/integration_tests/queries/test_mutual_follows.py deleted file mode 100644 index 360d6571ad1..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_mutual_follows.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -import pytest - -from integration_tests.utils import populate_mock_db -from src.queries.get_follow_intersection_users import get_follow_intersection_users -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -@pytest.fixture -def test_entities(): - return { - "users": [ - {"user_id": 51, "handle": "audius"}, - {"user_id": 1, "handle": "ray"}, - {"user_id": 2, "handle": "dave"}, - {"user_id": 3, "handle": "rando"}, - ], - "follows": [ - {"follower_user_id": 1, "followee_user_id": 51}, - {"follower_user_id": 1, "followee_user_id": 2}, - {"follower_user_id": 2, "followee_user_id": 51}, - {"follower_user_id": 3, "followee_user_id": 51}, - ], - } - - -def test_mutual_followers(app, test_entities): - # Add pagination variables to only retrieve first tip result - with app.test_request_context("?limit=10&offset=0"): - db = get_db() - populate_mock_db(db, test_entities) - with db.scoped_session(): - # Test first without filtering, should get the most recent tip - users = get_follow_intersection_users( - {"my_id": 1, "other_user_id": 51, "limit": 10, "offset": 0} - ) - - assert len(users) == 1 - assert users[0]["user_id"] == 2 - assert users[0]["does_current_user_follow"] == True diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_announcement_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_announcement_notification.py deleted file mode 100644 index b40779d5e86..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_announcement_notification.py +++ /dev/null @@ -1,78 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import ( - get_notifications, - get_unread_notification_count, -) -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def test_get_announcement_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(20)], - "notification": [ - { - "type": "announcement", - "group_id": "fake_group_id", - "specifier": "1", - "data": {"title": "my title", "short_description": "my desc"}, - "user_ids": [], - }, - { - "type": "repost", - "group_id": "fake_group_id_repost", - "specifier": "3", - "data": {"title": "my title", "short_description": "my desc"}, - "user_ids": [3], - }, - ], - } - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - unread_count = get_unread_notification_count(session, {"user_id": 1}) - assert unread_count == 1 - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 1 - assert u1_notifications[0]["type"] == "announcement" - assert u1_notifications[0]["group_id"] == "fake_group_id" - assert len(u1_notifications[0]["actions"]) == 1 - assert u1_notifications[0]["actions"][0]["data"] == { - "title": "my title", - "short_description": "my desc", - } - - -def test_get_announcement_before_account_created(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(20)], - "notification": [ - { - "type": "announcement", - "group_id": "fake_group_id", - "specifier": "1", - "data": {"title": "my title", "short_description": "my desc"}, - "user_ids": [], - "timestamp": datetime.now() - timedelta(hours=1), - }, - ], - } - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - unread_count = get_unread_notification_count(session, {"user_id": 1}) - assert unread_count == 0 - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 0 diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_follow_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_follow_notification.py deleted file mode 100644 index ada247a5a76..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_follow_notification.py +++ /dev/null @@ -1,123 +0,0 @@ -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_notification_groups, get_notifications -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - - -def test_get_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(4)], - "follows": [ - {"follower_user_id": i + 2, "followee_user_id": i + 1, "created_at": t2} - for i in range(3) - ], - "notification_seens": [ - {"user_id": 1, "seen_at": t3}, - {"user_id": 1, "seen_at": t1}, - {"user_id": 3, "seen_at": t3}, - ], - } - - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notification_groups = get_notification_groups(session, args) - assert len(u1_notification_groups) == 1 - assert u1_notification_groups[0]["group_id"] == "follow:1" - assert u1_notification_groups[0]["is_seen"] == True - assert u1_notification_groups[0]["seen_at"] == t1 - assert u1_notification_groups[0]["prev_seen_at"] == t3 - assert u1_notification_groups[0]["count"] == 1 - - # Test fetching a notification when a view is before and after - u1_notifications = get_notifications(session, args) - assert u1_notifications[0]["group_id"] == "follow:1" - assert u1_notifications[0]["is_seen"] == True - assert u1_notifications[0]["seen_at"] == t1 - assert u1_notifications[0]["actions"] == [ - { - "specifier": "2", - "type": "follow", - "group_id": "follow:1", - "timestamp": t2, - "data": {"follower_user_id": 2, "followee_user_id": 1}, - } - ] - - # Test fetching a notification when there are no views - u2_args = {"limit": 10, "user_id": 2} - u2_notification_groups = get_notification_groups(session, u2_args) - assert len(u2_notification_groups) == 1 - assert u2_notification_groups[0]["group_id"] == "follow:2" - assert u2_notification_groups[0]["is_seen"] == False - assert u2_notification_groups[0]["seen_at"] == None - assert u2_notification_groups[0]["prev_seen_at"] == None - assert u2_notification_groups[0]["count"] == 1 - - # Test fetching a notification when there is only a view after - u3_args = {"limit": 10, "user_id": 3} - u3_notifiations = get_notifications(session, u3_args) - assert u3_notifiations[0]["group_id"] == "follow:3" - assert u3_notifiations[0]["is_seen"] == False - assert u3_notifiations[0]["actions"] == [ - { - "specifier": "4", - "timestamp": t2, - "type": "follow", - "group_id": "follow:3", - "data": {"follower_user_id": 4, "followee_user_id": 3}, - } - ] - - -def test_get_many_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(20)], - "follows": [ - {"follower_user_id": i + 2, "followee_user_id": 1, "created_at": t1} - for i in range(4) - ] - + [ - {"follower_user_id": i + 2, "followee_user_id": 1, "created_at": t3} - for i in range(4, 8) - ], - "notification_seens": [{"user_id": 1, "seen_at": t2}], - } - - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 2 - assert u1_notifications[0]["group_id"] == "follow:1" - assert u1_notifications[0]["is_seen"] == False - assert len(u1_notifications[0]["actions"]) == 4 - for user_id_follower in range(2, 6): - assert any( - act["data"]["follower_user_id"] == user_id_follower - for act in u1_notifications[0]["actions"] - ) - - assert u1_notifications[1]["group_id"] == "follow:1" - assert u1_notifications[1]["is_seen"] == True - assert u1_notifications[1]["seen_at"] == t2 - assert len(u1_notifications[1]["actions"]) == 4 - for user_id_follower in range(6, 10): - assert any( - act["data"]["follower_user_id"] == user_id_follower - for act in u1_notifications[1]["actions"] - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_get_unread_notification_count.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_get_unread_notification_count.py deleted file mode 100644 index 933ce322400..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_get_unread_notification_count.py +++ /dev/null @@ -1,64 +0,0 @@ -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_unread_notification_count -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - - -def test_get_unread_notification_count(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(5)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - "notification_seens": [ - {"user_id": 1, "seen_at": t3}, - ], - } - - populate_mock_db(db_mock, test_entities) - - test_actions = { - "follows": [ - {"follower_user_id": 2, "followee_user_id": 1, "created_at": t1} - ], - "reposts": [ - { - "user_id": 3, - "repost_item_id": 1, - "repost_type": "track", - "created_at": t2, - }, - { - "user_id": 3, - "repost_item_id": 1, - "repost_type": "playlist", - "created_at": t3, - }, - ], - "saves": [ - { - "user_id": 4, - "save_item_id": 1, - "save_type": "track", - "created_at": t4, - } - ], - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = {"user_id": 1} - unread_count = get_unread_notification_count(session, args) - assert unread_count == 2 - - args = {"user_id": 3} - unread_count = get_unread_notification_count(session, args) - assert unread_count == 0 diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_paginate_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_paginate_notification.py deleted file mode 100644 index 12581ca4955..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_paginate_notification.py +++ /dev/null @@ -1,65 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_notifications -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -times = [t1 - timedelta(hours=i) for i in range(200)] - -logger = logging.getLogger(__name__) - - -def test_get_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(200)], - "tracks": [{"track_id": i, "owner_id": 1} for i in range(60)], - "notification_seens": [ - {"user_id": 1, "seen_at": times[i]} for i in range(0, 100, 3) - ], - } - - populate_mock_db(db_mock, test_entities) - test_actions = { - "follows": [ - { - "follower_user_id": i + 2, - "followee_user_id": 1, - "created_at": times[i], - } - for i in range(0, 80, 3) - ], - "saves": [ - { - "user_id": i, - "save_item_id": 1, - "save_type": "track", - "created_at": times[i], - } - for i in range(0, 60, 2) - ], - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notification_1 = get_notifications(session, args) - assert len(u1_notification_1) == 10 - sec_last_notification = u1_notification_1[8] - timestamp = sec_last_notification["seen_at"] - group_id_offset = sec_last_notification["group_id"] - args_2 = { - "limit": 4, - "user_id": 1, - "group_id": group_id_offset, - "timestamp": timestamp, - } - u1_notification_2 = get_notifications(session, args_2) - logger.info(u1_notification_2) - logger.info(u1_notification_1) - assert len(u1_notification_2) == 4 - assert u1_notification_2[0] == u1_notification_1[9] diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_remix_track_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_remix_track_notification.py deleted file mode 100644 index a4dcf4a34d2..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_remix_track_notification.py +++ /dev/null @@ -1,89 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.models.notifications.notification import Notification -from src.queries.get_notifications import get_notifications -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - -logger = logging.getLogger(__name__) - - -def test_remix_track_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(4)], - "plays": [{"user_id": i + 1} for i in range(4)], - "tracks": [{"track_id": 1, "owner_id": 1}], - } - - populate_mock_db(db_mock, test_entities) - - remix_entities = { - "tracks": [ - { - "track_id": 2, - "owner_id": 2, - "created_at": t1, - "updated_at": t1, - "remix_of": {"tracks": [{"parent_track_id": 1}]}, - } - ], - } - - populate_mock_db(db_mock, remix_entities) - - cosign_entities = { - "reposts": [ - { - "user_id": 1, - "repost_type": "track", - "repost_item_id": 2, - "created_at": t1, - } - ], - } - - populate_mock_db(db_mock, cosign_entities) - - with db_mock.scoped_session() as session: - nts = session.query(Notification).all() - logger.info(nts) - args_1 = {"limit": 10, "user_id": 1} - u1_notifs = get_notifications(session, args_1) - assert len(u1_notifs) == 1 - assert u1_notifs[0]["group_id"] == "remix:track:2:parent_track:1" - assert u1_notifs[0]["is_seen"] == False - assert u1_notifs[0]["actions"] == [ - { - "specifier": "2", - "type": "remix", - "timestamp": t1, - "group_id": "remix:track:2:parent_track:1", - "data": {"parent_track_id": 1, "track_id": 2}, - } - ] - - args_2 = {"limit": 10, "user_id": 2} - u2_notifs = get_notifications(session, args_2) - - assert len(u2_notifs) == 2 - assert u2_notifs[0]["group_id"] == "repost:2:type:track" - - assert u2_notifs[1]["group_id"] == "cosign:parent_track1:original_track:2" - assert u2_notifs[1]["actions"] == [ - { - "specifier": "1", - "type": "cosign", - "timestamp": t1, - "group_id": "cosign:parent_track1:original_track:2", - "data": {"track_id": 2, "track_owner_id": 2, "parent_track_id": 1}, - } - ] diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_notification.py deleted file mode 100644 index 449068e624a..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_notification.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_notifications -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - - -def test_get_repost_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(20)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - } - populate_mock_db(db_mock, test_entities) - - test_actions = { - "reposts": [ - { - "user_id": i + 2, - "repost_item_id": 1, - "repost_type": "track", - "created_at": t3, - } - for i in range(4) - ] - + [ - { - "user_id": i + 2, - "repost_item_id": 1, - "repost_type": "playlist", - "created_at": t4, - } - for i in range(11) - ] - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 3 - assert u1_notifications[0]["group_id"] == "repost:1:type:track" - assert u1_notifications[0]["is_seen"] == False - assert len(u1_notifications[0]["actions"]) == 4 - for reposter_user_id in range(2, 6): - assert any( - act["data"]["user_id"] == reposter_user_id - for act in u1_notifications[0]["actions"] - ) - - assert u1_notifications[1]["group_id"] == "repost:1:type:playlist" - assert u1_notifications[1]["is_seen"] == False - assert len(u1_notifications[1]["actions"]) == 11 - for reposter_user_id in range(2, 13): - assert any( - act["data"]["user_id"] == reposter_user_id - for act in u1_notifications[1]["actions"] - ) - - assert ( - u1_notifications[2]["group_id"] - == "milestone:PLAYLIST_REPOST_COUNT:id:1:threshold:10" - ) - assert u1_notifications[2]["is_seen"] == False - assert u1_notifications[2]["actions"][0]["type"] == "milestone" - assert ( - u1_notifications[2]["actions"][0]["data"]["type"] - == "PLAYLIST_REPOST_COUNT" - ) - assert u1_notifications[2]["actions"][0]["data"]["playlist_id"] == 1 - assert u1_notifications[2]["actions"][0]["data"]["threshold"] == 10 diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_repost_notifications.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_repost_notifications.py deleted file mode 100644 index 81698e05e94..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_repost_repost_notifications.py +++ /dev/null @@ -1,120 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import NotificationType, get_notifications -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def assert_notification( - notification, group_id, is_seen, actions_length, reposter_user_ids -): - assert notification["group_id"] == group_id - assert notification["is_seen"] == is_seen - assert len(notification["actions"]) == actions_length - for reposter_user_id in reposter_user_ids: - assert any( - action["data"]["user_id"] == reposter_user_id - for action in notification["actions"] - ) - - -def test_get_repost_of_repost_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(6)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - "follows": [ - {"follower_user_id": 4, "followee_user_id": 1}, - {"follower_user_id": 2, "followee_user_id": 4}, - {"follower_user_id": 3, "followee_user_id": 4}, - {"follower_user_id": 3, "followee_user_id": 5}, - {"follower_user_id": 5, "followee_user_id": 3}, - ], - } - populate_mock_db(db_mock, test_entities) - - test_actions = { - "reposts": [ - {"user_id": 4, "repost_item_id": 1, "repost_type": "track"}, - {"user_id": 4, "repost_item_id": 1, "repost_type": "playlist"}, - { - "user_id": 2, - "repost_item_id": 1, - "repost_type": "track", - "is_repost_of_repost": True, - }, - { - "user_id": 3, - "repost_item_id": 1, - "repost_type": "track", - "is_repost_of_repost": True, - }, - { - "user_id": 3, - "repost_item_id": 1, - "repost_type": "playlist", - "is_repost_of_repost": True, - }, - { - "user_id": 5, - "repost_item_id": 1, - "repost_type": "playlist", - "is_repost_of_repost": True, - }, - ] - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 4, - "valid_types": [NotificationType.REPOST_OF_REPOST], - } - user4_notifications = get_notifications(session, args) - assert len(user4_notifications) == 3 - assert_notification( - notification=user4_notifications[0], - group_id="repost_of_repost:1:type:playlist", - is_seen=False, - actions_length=1, - reposter_user_ids=[3], - ) - assert_notification( - notification=user4_notifications[1], - group_id="repost_of_repost:1:type:track", - is_seen=False, - actions_length=2, - reposter_user_ids=[2, 3], - ) - assert "repost_of_repost" not in user4_notifications[2]["group_id"] - - args = { - "limit": 10, - "user_id": 3, - "valid_types": [NotificationType.REPOST_OF_REPOST], - } - user3_notifications = get_notifications(session, args) - assert len(user3_notifications) == 2 - assert_notification( - notification=user3_notifications[0], - group_id="repost_of_repost:1:type:playlist", - is_seen=False, - actions_length=1, - reposter_user_ids=[5], - ) - assert "repost_of_repost" not in user3_notifications[1]["group_id"] - - args = { - "limit": 10, - "user_id": 1, - "valid_types": [NotificationType.REPOST_OF_REPOST], - } - user1_notifications = get_notifications(session, args) - for notif in user1_notifications: - assert "repost_of_repost" not in notif["group_id"] diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_notification.py deleted file mode 100644 index 59486f64aed..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_notification.py +++ /dev/null @@ -1,70 +0,0 @@ -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_notifications -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - - -def test_get_save_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(10)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - } - populate_mock_db(db_mock, test_entities) - - test_actions = { - "saves": [ - {"user_id": i + 2, "save_item_id": 1, "save_type": "track"} - for i in range(4) - ] - + [ - {"user_id": i + 2, "save_item_id": 1, "save_type": "playlist"} - for i in range(12) - ] - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 3 - - assert u1_notifications[0]["group_id"] == "save:1:type:playlist" - assert u1_notifications[0]["is_seen"] == False - assert len(u1_notifications[0]["actions"]) == 12 - for saver_user_id in range(2, 14): - assert any( - act["data"]["user_id"] == saver_user_id - for act in u1_notifications[0]["actions"] - ) - - assert ( - u1_notifications[1]["group_id"] - == "milestone:PLAYLIST_SAVE_COUNT:id:1:threshold:10" - ) - assert u1_notifications[1]["is_seen"] == False - assert u1_notifications[1]["actions"][0]["type"] == "milestone" - assert ( - u1_notifications[1]["actions"][0]["data"]["type"] - == "PLAYLIST_SAVE_COUNT" - ) - assert u1_notifications[1]["actions"][0]["data"]["playlist_id"] == 1 - assert u1_notifications[1]["actions"][0]["data"]["threshold"] == 10 - - assert u1_notifications[2]["group_id"] == "save:1:type:track" - assert u1_notifications[2]["is_seen"] == False - assert len(u1_notifications[2]["actions"]) == 4 - for saver_user_id in range(2, 6): - assert any( - act["data"]["user_id"] == saver_user_id - for act in u1_notifications[2]["actions"] - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_of_repost_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_of_repost_notification.py deleted file mode 100644 index 0c0bdf785a6..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_save_of_repost_notification.py +++ /dev/null @@ -1,118 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import NotificationType, get_notifications -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def assert_notification( - notification, group_id, is_seen, actions_length, reposter_user_ids -): - assert notification["group_id"] == group_id - assert notification["is_seen"] == is_seen - assert len(notification["actions"]) == actions_length - for reposter_user_id in reposter_user_ids: - assert any( - action["data"]["user_id"] == reposter_user_id - for action in notification["actions"] - ) - - -def test_get_save_of_repost_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(6)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - "follows": [ - {"follower_user_id": 4, "followee_user_id": 1}, - {"follower_user_id": 2, "followee_user_id": 4}, - {"follower_user_id": 3, "followee_user_id": 4}, - {"follower_user_id": 3, "followee_user_id": 5}, - {"follower_user_id": 5, "followee_user_id": 3}, - ], - } - populate_mock_db(db_mock, test_entities) - - test_reposts = { - "reposts": [ - {"user_id": 4, "repost_item_id": 1, "repost_type": "track"}, - {"user_id": 4, "repost_item_id": 1, "repost_type": "playlist"}, - ] - } - test_saves = { - "saves": [ - { - "user_id": 2, - "save_item_id": 1, - "save_type": "track", - "is_save_of_repost": True, - }, - { - "user_id": 3, - "save_item_id": 1, - "save_type": "track", - "is_save_of_repost": True, - }, - { - "user_id": 3, - "save_item_id": 1, - "save_type": "playlist", - "is_save_of_repost": True, - }, - { - "user_id": 5, - "save_item_id": 1, - "save_type": "playlist", - "is_save_of_repost": True, - }, - ], - } - populate_mock_db(db_mock, test_reposts) - populate_mock_db(db_mock, test_saves) - - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 4, - "valid_types": [NotificationType.SAVE_OF_REPOST], - } - user4_notifications = get_notifications(session, args) - assert len(user4_notifications) == 3 - assert_notification( - notification=user4_notifications[0], - group_id="save_of_repost:1:type:playlist", - is_seen=False, - actions_length=1, - reposter_user_ids=[3], - ) - assert_notification( - notification=user4_notifications[1], - group_id="save_of_repost:1:type:track", - is_seen=False, - actions_length=2, - reposter_user_ids=[2, 3], - ) - assert "save_of_repost" not in user4_notifications[2]["group_id"] - - args = { - "limit": 10, - "user_id": 3, - "valid_types": [NotificationType.SAVE_OF_REPOST], - } - user3_notifications = get_notifications(session, args) - for notif in user3_notifications: - assert "save_of_repost" not in notif["group_id"] - - args = { - "limit": 10, - "user_id": 1, - "valid_types": [NotificationType.SAVE_OF_REPOST], - } - user1_notifications = get_notifications(session, args) - for notif in user1_notifications: - assert "save_of_repost" not in notif["group_id"] diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_tastemaker_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_tastemaker_notification.py deleted file mode 100644 index 7d534e520d1..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_tastemaker_notification.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import NotificationType, get_notifications -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def assert_notification( - notification, group_id, is_seen, actions_length, user_ids_to_notify -): - assert notification["group_id"] == group_id - assert notification["is_seen"] == is_seen - assert len(notification["actions"]) == actions_length - for user_id in user_ids_to_notify: - assert any( - action["data"]["tastemaker_user_id"] == user_id - for action in notification["actions"] - ) - - -def test_get_tastemaker_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(6)], - "tracks": [{"track_id": 1, "owner_id": 1}], - "playlists": [{"playlist_id": 1, "playlist_owner_id": 1}], - } - populate_mock_db(db_mock, test_entities) - - test_actions = { - "notification": [ - { - "user_ids": [2], - "type": "tastemaker", - "group_id": "fake-group-id", - "specifier": 1, - "data": { - "tastemaker_item_id": 1, - "tastemaker_item_owner_id": 1, - "action": "repost", - "tastemaker_item_type": "track", - "tastemaker_user_id": 2, - }, - }, - ] - } - populate_mock_db(db_mock, test_actions) - - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 2, - "valid_types": [], - } - user2_notifications = get_notifications(session, args) - for notification in user2_notifications: - assert notification["type"] != "tastemaker" - - args = { - "limit": 10, - "user_id": 2, - "valid_types": [NotificationType.TASTEMAKER], - } - user2_notifications = get_notifications(session, args) - assert len(user2_notifications) == 1 - assert_notification( - notification=user2_notifications[0], - group_id="fake-group-id", - is_seen=False, - actions_length=1, - user_ids_to_notify=[2], - ) diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_playlist_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_playlist_notification.py deleted file mode 100644 index ddc5e0d3652..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_playlist_notification.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import ( - get_notifications, - get_unread_notification_count, -) -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def test_get_announcement_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "users": [{"user_id": i + 1} for i in range(20)], - "notification": [ - { - "type": "track_added_to_playlist", - "group_id": "track_added_to_playlist:playlist_id:1230335492:track_id:1553038297:blocknumber:41260831", - "specifier": "1", - "data": {"track_id": 23, "playlist_id": 32, "playlist_owner_id": 3}, - "user_ids": [1], - }, - ], - } - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - unread_count = get_unread_notification_count(session, {"user_id": 1}) - assert unread_count == 1 - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 1 - assert u1_notifications[0]["type"] == "track_added_to_playlist" - assert ( - u1_notifications[0]["group_id"] - == "track_added_to_playlist:playlist_id:1230335492:track_id:1553038297:blocknumber:41260831" - ) - assert len(u1_notifications[0]["actions"]) == 1 - assert u1_notifications[0]["actions"][0]["data"] == { - "track_id": 23, - "playlist_id": 32, - "playlist_owner_id": 3, - } diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_playlist_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_playlist_notification.py deleted file mode 100644 index e532f84e46a..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_playlist_notification.py +++ /dev/null @@ -1,91 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import NotificationType, get_notifications -from src.utils.db_session import get_db - - -def test_get_trending_playlist_notifications(app): - with app.app_context(): - db_mock = get_db() - - entities = { - "users": [{"user_id": i + 1} for i in range(6)], - "playlists": [ - {"playlist_id": 1, "playlist_owner_id": 1}, - {"playlist_id": 2, "playlist_owner_id": 2}, - ], - "notification": [ - { - "user_ids": [1], - "type": "trending_playlist", - "group_id": "group-id-1", - "specifier": 1, - "data": { - "time_range": "week", - "genre": "all", - "rank": "1", - "playlist_id": 1, - }, - }, - { - "user_ids": [2], - "type": "trending_playlist", - "group_id": "group-id-2", - "specifier": 1, - "data": { - "time_range": "week", - "genre": "all", - "rank": "2", - "playlist_id": 2, - }, - }, - ], - } - populate_mock_db(db_mock, entities) - - # When valid_types is [] we don't select any trending_playlist notifications - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 1, - "valid_types": [], - } - user1_notifications = get_notifications(session, args) - assert len(user1_notifications) == 0 - - # When valid_types is ["trending_playlist"] and user_id is 1, - # select notifs for user1 - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 1, - "valid_types": [NotificationType.TRENDING_PLAYLIST], - } - - user1_notifications = get_notifications(session, args) - assert len(user1_notifications) == 1 - - user1_notification = user1_notifications[0] - assert user1_notification["type"] == "trending_playlist" - - notification_action = user1_notification["actions"][0] - assert notification_action["data"]["rank"] == "1" - assert notification_action["data"]["playlist_id"] == 1 - - # When valid_types is ["trending_playlist"] and user_id is 2, - # select notifs for user2 - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 2, - "valid_types": [NotificationType.TRENDING_PLAYLIST], - } - - user2_notifications = get_notifications(session, args) - assert len(user2_notifications) == 1 - - user2_notification = user2_notifications[0] - assert user2_notification["type"] == "trending_playlist" - - notification_action = user2_notification["actions"][0] - assert notification_action["data"]["rank"] == "2" - assert notification_action["data"]["playlist_id"] == 2 diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_underground_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_underground_notification.py deleted file mode 100644 index 39bc9ca685e..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_trending_underground_notification.py +++ /dev/null @@ -1,88 +0,0 @@ -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import NotificationType, get_notifications -from src.utils.db_session import get_db - - -def test_get_trending_underground_notifications(app): - with app.app_context(): - db_mock = get_db() - - entities = { - "users": [{"user_id": i + 1} for i in range(6)], - "tracks": [{"track_id": 1, "owner_id": 1}, {"track_id": 2, "owner_id": 2}], - "notification": [ - { - "user_ids": [1], - "type": "trending_underground", - "group_id": "group-id-1", - "specifier": 1, - "data": { - "time_range": "week", - "genre": "all", - "rank": "1", - "track_id": 1, - }, - }, - { - "user_ids": [2], - "type": "trending_underground", - "group_id": "group-id-2", - "specifier": 1, - "data": { - "time_range": "week", - "genre": "all", - "rank": "2", - "track_id": 2, - }, - }, - ], - } - populate_mock_db(db_mock, entities) - - # When valid_types is [] we don't select any trending_underground notifications - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 1, - "valid_types": [], - } - user1_notifications = get_notifications(session, args) - assert len(user1_notifications) == 0 - - # When valid_types is ["trending_underground"] and user_id is 1, - # select notifs for user1 - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 1, - "valid_types": [NotificationType.TRENDING_UNDERGROUND], - } - - user2_notifications = get_notifications(session, args) - assert len(user2_notifications) == 1 - - user1_notification = user2_notifications[0] - assert user1_notification["type"] == "trending_underground" - - notification_action = user1_notification["actions"][0] - assert notification_action["data"]["rank"] == "1" - assert notification_action["data"]["track_id"] == 1 - - # When valid_types is ["trending_underground"] and user_id is 2, - # select notifs for user2 - with db_mock.scoped_session() as session: - args = { - "limit": 10, - "user_id": 2, - "valid_types": [NotificationType.TRENDING_UNDERGROUND], - } - - user2_notifications = get_notifications(session, args) - assert len(user2_notifications) == 1 - - user2_notification = user2_notifications[0] - assert user2_notification["type"] == "trending_underground" - - notification_action = user2_notification["actions"][0] - assert notification_action["data"]["rank"] == "2" - assert notification_action["data"]["track_id"] == 2 diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_user_tier_change_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_user_tier_change_notification.py deleted file mode 100644 index ac4e29ea715..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_user_tier_change_notification.py +++ /dev/null @@ -1,45 +0,0 @@ -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.queries.get_notifications import get_notifications -from src.utils.db_session import get_db - -t1 = datetime(2020, 10, 10, 10, 35, 0) -t2 = t1 - timedelta(hours=1) -t3 = t1 - timedelta(hours=2) -t4 = t1 - timedelta(hours=3) - - -def test_get_save_notifications(app): - with app.app_context(): - db_mock = get_db() - - test_entities = { - "user_balance_changes": [ - { - "user_id": 1, - "blocknumber": 10, - # No tier change, none -> none - "previous_balance": 0, - "current_balance": 20000000000000000000, - }, - ], - "users": [{"user_id": i + 1} for i in range(2)], - } - populate_mock_db(db_mock, test_entities) - - with db_mock.scoped_session() as session: - args = {"limit": 10, "user_id": 1} - u1_notifications = get_notifications(session, args) - assert len(u1_notifications) == 1 - assert ( - u1_notifications[0]["group_id"] - == "tier_change:user_id:1:tier:bronze:blocknumber:10" - ) - assert u1_notifications[0]["is_seen"] == False - assert len(u1_notifications[0]["actions"]) == 1 - u1_notifications[0]["actions"][0]["data"] = { - "new_tier": "bronze", - "new_tier_value": 10, - "current_value": "20000000000000000000", - } diff --git a/packages/discovery-provider/integration_tests/queries/test_populate_user_metadata.py b/packages/discovery-provider/integration_tests/queries/test_populate_user_metadata.py deleted file mode 100644 index 9b0f68b169a..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_populate_user_metadata.py +++ /dev/null @@ -1,164 +0,0 @@ -import logging - -from integration_tests.utils import populate_mock_db -from src.models.playlists.playlist import Playlist -from src.queries import response_name_constants -from src.queries.get_top_users import _get_top_users -from src.queries.query_helpers import populate_user_metadata -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -def test_populate_user_metadata(app): - """Tests that populate_user_metadata works after aggregate_user update""" - with app.app_context(): - db = get_db() - - test_entities = { - "tracks": [ - {"track_id": 1, "owner_id": 1}, - {"track_id": 2, "owner_id": 1}, - {"track_id": 3, "owner_id": 2}, - {"track_id": 4, "owner_id": 2}, - {"track_id": 5, "owner_id": 2}, - {"track_id": 6, "owner_id": 2}, - {"track_id": 7, "owner_id": 3}, - {"track_id": 8, "owner_id": 3}, - {"track_id": 9, "is_unlisted": True, "owner_id": 3}, - ], - "playlists": [ - {"playlist_id": 1, "playlist_owner_id": 1}, - {"playlist_id": 2, "playlist_owner_id": 1}, - {"playlist_id": 3, "is_album": True, "playlist_owner_id": 1}, - {"playlist_id": 4, "playlist_owner_id": 2}, - { - "playlist_id": 5, - "is_delete": False, - "playlist_owner_id": 2, - }, - {"playlist_id": 6, "is_album": True, "playlist_owner_id": 3}, - {"playlist_id": 6, "is_private": True, "playlist_owner_id": 3}, - ], - "users": [ - {"user_id": 1, "handle": "user1", "wallet": "0x111"}, - {"user_id": 2, "handle": "user2", "wallet": "0x222"}, - {"user_id": 3, "handle": "user3", "wallet": "0x333"}, - {"user_id": 4, "handle": "user4", "wallet": "0x444"}, - ], - "follows": [ - {"follower_user_id": 1, "followee_user_id": 2}, - {"follower_user_id": 1, "followee_user_id": 3}, - {"follower_user_id": 2, "followee_user_id": 3}, - ], - "reposts": [ - {"repost_item_id": 1, "repost_type": "track", "user_id": 2}, - {"repost_item_id": 1, "repost_type": "playlist", "user_id": 2}, - {"repost_item_id": 1, "repost_type": "track", "user_id": 3}, - {"repost_item_id": 1, "repost_type": "playlist", "user_id": 3}, - {"repost_item_id": 4, "repost_type": "track", "user_id": 1}, - {"repost_item_id": 5, "repost_type": "track", "user_id": 1}, - {"repost_item_id": 6, "repost_type": "track", "user_id": 1}, - ], - "saves": [ - {"save_item_id": 1, "save_type": "track", "user_id": 2}, - {"save_item_id": 1, "save_type": "playlist", "user_id": 2}, - {"save_item_id": 1, "save_type": "track", "user_id": 3}, - {"save_item_id": 1, "save_type": "playlist", "user_id": 3}, - {"save_item_id": 4, "save_type": "track", "user_id": 1}, - {"save_item_id": 5, "save_type": "track", "user_id": 1}, - {"save_item_id": 6, "save_type": "track", "user_id": 1}, - ], - } - - populate_mock_db(db, test_entities) - with db.scoped_session() as session: - playlist_to_delete = ( - session.query(Playlist).filter(Playlist.playlist_id == 5).first() - ) - playlist_to_delete.is_delete = True - - with db.scoped_session() as session: - user_ids = [1, 2, 3, 4, 5] - users = [ - {"user_id": 1, "wallet": "0x111", "is_verified": False}, - {"user_id": 2, "wallet": "0x222", "is_verified": False}, - {"user_id": 3, "wallet": "0x333", "is_verified": False}, - {"user_id": 4, "wallet": "0x444", "is_verified": False}, - {"user_id": 5, "wallet": "0x555", "is_verified": False}, - ] - - users = populate_user_metadata(session, user_ids, users, 3) - assert len(users) == 5 - - assert users[0]["user_id"] == 1 - assert users[0][response_name_constants.track_count] == 2 - assert users[0][response_name_constants.playlist_count] == 2 - assert users[0][response_name_constants.album_count] == 1 - assert users[0][response_name_constants.follower_count] == 0 - assert users[0][response_name_constants.followee_count] == 2 - assert users[0][response_name_constants.repost_count] == 3 - - assert users[1]["user_id"] == 2 - assert users[1][response_name_constants.track_count] == 4 - assert users[1][response_name_constants.playlist_count] == 2 - assert users[1][response_name_constants.album_count] == 0 - assert users[1][response_name_constants.follower_count] == 1 - assert users[1][response_name_constants.followee_count] == 1 - assert users[1][response_name_constants.repost_count] == 2 - - assert users[2]["user_id"] == 3 - assert users[2][response_name_constants.track_count] == 2 - assert users[2][response_name_constants.playlist_count] == 0 - assert users[2][response_name_constants.album_count] == 1 - assert users[2][response_name_constants.follower_count] == 2 - assert users[2][response_name_constants.followee_count] == 0 - assert users[2][response_name_constants.repost_count] == 2 - - assert users[3]["user_id"] == 4 - assert users[3][response_name_constants.track_count] == 0 - assert users[3][response_name_constants.playlist_count] == 0 - assert users[3][response_name_constants.album_count] == 0 - assert users[3][response_name_constants.follower_count] == 0 - assert users[3][response_name_constants.followee_count] == 0 - assert users[3][response_name_constants.repost_count] == 0 - - assert users[4]["user_id"] == 5 - assert users[4][response_name_constants.track_count] == 0 - assert users[4][response_name_constants.playlist_count] == 0 - assert users[4][response_name_constants.album_count] == 0 - assert users[4][response_name_constants.follower_count] == 0 - assert users[4][response_name_constants.followee_count] == 0 - assert users[4][response_name_constants.repost_count] == 0 - - curr_user_ids = [1, 2, 3] - curr_users = [ - {"user_id": 1, "wallet": "0x111", "is_verified": False}, - {"user_id": 2, "wallet": "0x222", "is_verified": False}, - {"user_id": 3, "wallet": "0x333", "is_verified": False}, - ] - - users = populate_user_metadata(session, curr_user_ids, curr_users, 1) - assert len(users) == 3 - - assert users[0]["user_id"] == 1 - assert users[0][response_name_constants.does_current_user_follow] == False - assert users[0][response_name_constants.current_user_followee_follow_count] == 0 - assert users[0][response_name_constants.balance] == "0" - assert users[0][response_name_constants.associated_wallets_balance] == "0" - - assert users[1]["user_id"] == 2 - assert users[1][response_name_constants.does_current_user_follow] == True - assert users[1][response_name_constants.current_user_followee_follow_count] == 0 - assert users[1][response_name_constants.balance] == "0" - assert users[1][response_name_constants.associated_wallets_balance] == "0" - - assert users[2]["user_id"] == 3 - assert users[2][response_name_constants.does_current_user_follow] == True - assert users[2][response_name_constants.current_user_followee_follow_count] == 1 - assert users[2][response_name_constants.balance] == "0" - assert users[2][response_name_constants.associated_wallets_balance] == "0" - - # get_top_users: should return only artists, most followers first - top_user_ids = [u["user_id"] for u in _get_top_users(session, 1, 100, 0)] - assert top_user_ids == [3, 2, 1] diff --git a/packages/discovery-provider/integration_tests/queries/test_undisbursed_challenges.py b/packages/discovery-provider/integration_tests/queries/test_undisbursed_challenges.py deleted file mode 100644 index ac1918c9c73..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_undisbursed_challenges.py +++ /dev/null @@ -1,239 +0,0 @@ -from datetime import datetime - -from integration_tests.utils import populate_mock_db_blocks -from src.models.rewards.challenge import Challenge, ChallengeType -from src.models.rewards.user_challenge import UserChallenge -from src.models.users.user import User -from src.models.users.user_bank import UserBankAccount -from src.queries.get_undisbursed_challenges import get_undisbursed_challenges -from src.utils.db_session import get_db - - -def setup_challenges(app): - with app.app_context(): - db = get_db() - populate_mock_db_blocks(db, 99, 110) - challenges = [ - Challenge( - id="test_challenge_1", - type=ChallengeType.numeric, - amount="5", - step_count=3, - active=False, - starting_block=100, - ), - Challenge( - id="test_challenge_2", - type=ChallengeType.boolean, - amount="5", - active=True, - starting_block=100, - ), - Challenge( - id="test_challenge_3", - type=ChallengeType.aggregate, - amount="5", - active=True, - starting_block=100, - ), - ] - - users = [ - User( - blockhash=hex(99), - blocknumber=99, - txhash=f"xyz{i}", - user_id=i, - is_current=True, - handle=f"TestHandle{i}", - handle_lc=f"testhandle{i}", - wallet=f"0x{i}", - is_verified=False, - name=f"test_name{i}", - created_at=datetime.now(), - updated_at=datetime.now(), - ) - for i in range(7) - ] - user_bank_accounts = [ - UserBankAccount( - signature=f"0x{i}", - ethereum_address=users[i].wallet, - bank_account=f"0x{i}", - created_at=datetime.now(), - ) - for i in range(7) - ] - - user_challenges = [ - UserChallenge( - challenge_id="test_challenge_1", - user_id=1, - specifier="1", - is_complete=False, - current_step_count=1, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - UserChallenge( - challenge_id="test_challenge_1", - user_id=2, - specifier="2", - is_complete=True, - current_step_count=3, - completed_blocknumber=100, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - UserChallenge( - challenge_id="test_challenge_2", - user_id=3, - specifier="3", - is_complete=False, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - UserChallenge( - challenge_id="test_challenge_2", - user_id=4, - specifier="4", - is_complete=True, - completed_blocknumber=102, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - UserChallenge( - challenge_id="test_challenge_2", - user_id=5, - specifier="5", - is_complete=True, - completed_blocknumber=102, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - UserChallenge( - challenge_id="test_challenge_3", - user_id=6, - specifier="6", - is_complete=True, - completed_blocknumber=100, - amount=5, - created_at="2023-10-16 17:51:31.105065+00", - completed_at="2023-10-16 17:51:31.105065+00", - ), - ] - - with db.scoped_session() as session: - session.add_all(challenges) - session.flush() - session.add_all(users) - session.add_all(user_bank_accounts) - session.add_all(user_challenges) - - -def test_undisbursed_challenges(app): - setup_challenges(app) - - with app.app_context(): - db = get_db() - - with db.scoped_session() as session: - # Test that all undisbursed challenges are returned in order - undisbursed = get_undisbursed_challenges( - session, - { - "user_id": None, - "limit": 10, - "offset": 0, - "completed_blocknumber": 99, - "challenge_id": None, - }, - ) - - expected = [ - { - "challenge_id": "test_challenge_3", - "user_id": 6, - "specifier": "6", - "amount": "5", - "completed_blocknumber": 100, - "handle": "TestHandle6", - "wallet": "0x6", - "created_at": "2023-10-16 17:51:31.105065+00:00", - "completed_at": "2023-10-16 17:51:31.105065", - "cooldown_days": None, - }, - { - "challenge_id": "test_challenge_2", - "user_id": 4, - "specifier": "4", - "amount": "5", - "completed_blocknumber": 102, - "handle": "TestHandle4", - "wallet": "0x4", - "created_at": "2023-10-16 17:51:31.105065+00:00", - "completed_at": "2023-10-16 17:51:31.105065", - "cooldown_days": None, - }, - { - "challenge_id": "test_challenge_2", - "user_id": 5, - "specifier": "5", - "amount": "5", - "completed_blocknumber": 102, - "handle": "TestHandle5", - "wallet": "0x5", - "created_at": "2023-10-16 17:51:31.105065+00:00", - "completed_at": "2023-10-16 17:51:31.105065", - "cooldown_days": None, - }, - ] - assert expected == undisbursed - - # Test that it filters correctly by user_id - undisbursed = get_undisbursed_challenges( - session, - { - "user_id": 6, - "limit": 10, - "offset": 0, - "completed_blocknumber": 99, - "challenge_id": None, - }, - ) - - expected = [ - { - "challenge_id": "test_challenge_3", - "user_id": 6, - "specifier": "6", - "amount": "5", - "completed_blocknumber": 100, - "handle": "TestHandle6", - "wallet": "0x6", - "created_at": "2023-10-16 17:51:31.105065+00:00", - "completed_at": "2023-10-16 17:51:31.105065", - "cooldown_days": None, - }, - ] - assert expected == undisbursed - - # Test that it filters correctly by user_id & completed blocknumber - undisbursed = get_undisbursed_challenges( - session, - { - "user_id": 6, - "limit": 10, - "offset": 0, - "completed_blocknumber": 101, - "challenge_id": None, - }, - ) - - expected = [] - assert expected == undisbursed diff --git a/packages/discovery-provider/integration_tests/queries/test_user_playlist_update.py b/packages/discovery-provider/integration_tests/queries/test_user_playlist_update.py deleted file mode 100644 index c1690b28248..00000000000 --- a/packages/discovery-provider/integration_tests/queries/test_user_playlist_update.py +++ /dev/null @@ -1,103 +0,0 @@ -import logging -from datetime import datetime - -from integration_tests.utils import populate_mock_db -from src.queries.get_user_playlist_update import get_user_playlist_update -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -t1 = datetime.fromtimestamp(10000000) -t2 = datetime.fromtimestamp(10000001) -t3 = datetime.fromtimestamp(10000002) -t4 = datetime.fromtimestamp(10000003) - - -def test_user_playlist_update(app): - """Tests that fetching updated playlists for users works""" - with app.app_context(): - db = get_db() - - test_entities = { - "playlists": [ - {"playlist_id": playlist_id, "updated_at": t2} - for playlist_id in range(1, 20) - ], - "users": [{"user_id": user_id} for user_id in range(1, 20)], - "saves": [ - { - "user_id": 1, - "save_item_id": 1, - "save_type": "playlist", - "created_at": t1, - }, - { - "user_id": 1, - "save_item_id": 2, - "save_type": "playlist", - "created_at": t1, - }, - { - "user_id": 1, - "save_item_id": 3, - "save_type": "playlist", - "created_at": t1, - }, - { - "user_id": 1, - "save_item_id": 4, - "save_type": "playlist", - "created_at": t1, - }, - { - "user_id": 2, - "save_item_id": 2, - "save_type": "playlist", - "created_at": t1, - }, - { - "user_id": 3, - "save_item_id": 2, - "save_type": "playlist", - "created_at": t3, - }, - ], - "playlist_seens": [ - {"is_current": True, "user_id": 1, "playlist_id": 1, "seen_at": t1}, - {"is_current": True, "user_id": 1, "playlist_id": 2, "seen_at": t2}, - {"is_current": True, "user_id": 1, "playlist_id": 3, "seen_at": t3}, - {"is_current": True, "user_id": 1, "playlist_id": 4, "seen_at": t1}, - ], - } - - populate_mock_db(db, test_entities) - - user_id = 1 - playlist_updates = get_user_playlist_update(user_id) - assert playlist_updates == [ - { - "playlist_id": 1, - "updated_at": t2, - "last_seen_at": t1, - }, - { - "playlist_id": 4, - "updated_at": t2, - "last_seen_at": t1, - }, - ] - - user_id = 2 - playlist_updates = get_user_playlist_update(user_id) - assert playlist_updates == [ - { - "playlist_id": 2, - "updated_at": t2, - "last_seen_at": None, - } - ] - - user_id = 3 - playlist_updates = get_user_playlist_update(user_id) - assert playlist_updates == [] diff --git a/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ending_soon_notification.py b/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ending_soon_notification.py deleted file mode 100644 index 2ffce034bfe..00000000000 --- a/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ending_soon_notification.py +++ /dev/null @@ -1,166 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.models.notifications.notification import Notification -from src.queries.get_notifications import NotificationType -from src.tasks.remix_contest_notifications.artist_remix_contest_ending_soon import ( - create_artist_remix_contest_ending_soon_notifications, -) -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -TEST_EVENT_CREATOR_ID = 1 -TEST_TRACK_ID = 100 -TEST_FOLLOWER_ID = 2 -TEST_FAVORITER_ID = 3 - - -def test_artist_remix_contest_ending_soon_notification_for_artist(app): - """Test that artist remix contest ending soon notification is created for the event creator only""" - with app.app_context(): - db = get_db() - - now = datetime.now() - end_date = now + timedelta(hours=24) - - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - }, - ], - "follows": [ - { - "follower_user_id": TEST_FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "saves": [ - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": end_date, - }, - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_artist_remix_contest_ending_soon_notifications(session) - notifications = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.ARTIST_REMIX_CONTEST_ENDING_SOON - ) - .all() - ) - assert len(notifications) == 1 - notification = notifications[0] - assert notification.user_ids == [TEST_EVENT_CREATOR_ID] - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.ARTIST_REMIX_CONTEST_ENDING_SOON - assert notification.group_id.startswith("artist_remix_contest_ending_soon:") - - -def test_artist_remix_contest_ending_soon_notification_private_track(app): - """Test that no notification is created for a remix contest on a private (unlisted) track for the artist""" - with app.app_context(): - db = get_db() - - now = datetime.now() - end_date = now + timedelta(hours=24) - PRIVATE_TRACK_ID = 200 - PRIVATE_TRACK_OWNER_ID = 5 - - entities = { - "users": [ - { - "user_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": PRIVATE_TRACK_ID, - "owner_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Mark as private - "created_at": now, - "updated_at": now, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": PRIVATE_TRACK_OWNER_ID, - "entity_id": PRIVATE_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": end_date, - }, - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_artist_remix_contest_ending_soon_notifications(session) - notifications = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.ARTIST_REMIX_CONTEST_ENDING_SOON - ) - .all() - ) - # Should not notify the artist for private tracks - assert len(notifications) == 0 diff --git a/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_submissions_notification.py b/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_submissions_notification.py deleted file mode 100644 index 3bcbe07fd91..00000000000 --- a/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_submissions_notification.py +++ /dev/null @@ -1,131 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.models.notifications.notification import Notification -from src.queries.get_notifications import NotificationType -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -TEST_CONTEST_CREATOR_ID = 1 -TEST_PARENT_TRACK_ID = 100 -TEST_EVENT_ID = 200 - - -def make_remix(track_id, owner_id, created_at): - return { - "track_id": track_id, - "owner_id": owner_id, - "is_current": True, - "is_delete": False, - "created_at": created_at, - "updated_at": created_at, - "remix_of": {"tracks": [{"parent_track_id": TEST_PARENT_TRACK_ID}]}, - } - - -def get_milestone_notifications(session): - return ( - session.query(Notification) - .filter(Notification.type == NotificationType.ARTIST_REMIX_CONTEST_SUBMISSIONS) - .all() - ) - - -def test_artist_remix_contest_submissions_milestones(app): - """Test that milestone notifications are created for 1, 10, 50 submissions after contest start""" - with app.app_context(): - db = get_db() - - now = datetime.now() - before = now - timedelta(days=1) - - # Insert contest creator and parent track - entities = { - "users": [ - { - "user_id": TEST_CONTEST_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - } - ], - "tracks": [ - { - "track_id": TEST_PARENT_TRACK_ID, - "owner_id": TEST_CONTEST_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - } - ], - } - populate_mock_db(db, entities) - - # Insert contest event - event_entities = { - "events": [ - { - "event_id": TEST_EVENT_ID, - "event_type": "remix_contest", - "user_id": TEST_CONTEST_CREATOR_ID, - "entity_id": TEST_PARENT_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": now + timedelta(days=7), - } - ] - } - populate_mock_db(db, event_entities) - - with db.scoped_session() as session: - # Insert 1 remix before contest start (should not count) - remix_entities = {"tracks": [make_remix(1000, 2, before)]} - populate_mock_db(db, remix_entities) - notifications = get_milestone_notifications(session) - assert len(notifications) == 0 - - # Insert 1st remix after contest start (should trigger 1 milestone) - remix_entities = {"tracks": [make_remix(1001, 3, now + timedelta(minutes=1))]} - populate_mock_db(db, remix_entities) - notifications = get_milestone_notifications(session) - milestones = {n.data["milestone"] for n in notifications} - assert milestones == {1} - assert len(notifications) == 1 - - # Insert 9 more remixes (total 10 after contest start) - remix_entities = { - "tracks": [ - make_remix(1002 + i, 4 + i, now + timedelta(minutes=2 + i)) - for i in range(9) - ] - } - populate_mock_db(db, remix_entities) - notifications = get_milestone_notifications(session) - milestones = {n.data["milestone"] for n in notifications} - assert milestones == {1, 10} - assert len([n for n in notifications if n.data["milestone"] == 10]) == 1 - - # Insert 40 more remixes (total 50 after contest start) - remix_entities = { - "tracks": [ - make_remix(1011 + i, 13 + i, now + timedelta(minutes=11 + i)) - for i in range(40) - ] - } - populate_mock_db(db, remix_entities) - notifications = get_milestone_notifications(session) - milestones = {n.data["milestone"] for n in notifications} - assert milestones == {1, 10, 50} - assert len([n for n in notifications if n.data["milestone"] == 50]) == 1 - - # Final check: only 3 notifications, and all are for the contest creator and correct event/track - assert len(notifications) == 3 - for n in notifications: - assert n.user_ids == [TEST_CONTEST_CREATOR_ID] - assert n.data["event_id"] == TEST_EVENT_ID - assert n.data["entity_id"] == TEST_PARENT_TRACK_ID diff --git a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ended_notification.py b/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ended_notification.py deleted file mode 100644 index d50dcbf99d3..00000000000 --- a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ended_notification.py +++ /dev/null @@ -1,274 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.models.notifications.notification import Notification -from src.queries.get_notifications import NotificationType -from src.tasks.remix_contest_notifications.fan_remix_contest_ended import ( - create_fan_remix_contest_ended_notifications, -) -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -TEST_EVENT_CREATOR_ID = 1 -TEST_TRACK_ID = 100 -TEST_REMIXER_ID_1 = 2 -TEST_REMIXER_ID_2 = 3 - - -def test_fan_remix_contest_ended_notification_for_remixers(app): - """Test that remix contest ended notification is created for all remixers of the contest track""" - with app.app_context(): - db = get_db() - - now = datetime.now() - event_time = now - timedelta(hours=1) - remix_time = now - timedelta(minutes=30) - - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_REMIXER_ID_1, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_REMIXER_ID_2, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - }, - # Remix 1 by REMIXER_ID_1 - { - "track_id": 200, - "owner_id": TEST_REMIXER_ID_1, - "is_current": True, - "is_delete": False, - "created_at": remix_time, - "updated_at": remix_time, - "remix_of": {"tracks": [{"parent_track_id": TEST_TRACK_ID}]}, - }, - # Remix 2 by REMIXER_ID_2 - { - "track_id": 201, - "owner_id": TEST_REMIXER_ID_2, - "is_current": True, - "is_delete": False, - "created_at": remix_time, - "updated_at": remix_time, - "remix_of": {"tracks": [{"parent_track_id": TEST_TRACK_ID}]}, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "end_date": event_time, - "created_at": event_time, - "updated_at": event_time, - } - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ended_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDED) - .all() - ) - notified_user_ids = set() - for notification in notifications: - notified_user_ids.update(notification.user_ids) - # entity_user_id should be the original artist - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_ENDED - assert notification.group_id.startswith("fan_remix_contest_ended:") - # Should notify both remixers - assert TEST_REMIXER_ID_1 in notified_user_ids - assert TEST_REMIXER_ID_2 in notified_user_ids - assert len(notified_user_ids) == 2 - - -def test_fan_remix_contest_ended_notification_no_duplicate_for_multiple_remixes(app): - """Test that a user who submitted multiple remixes only gets one notification""" - with app.app_context(): - db = get_db() - - now = datetime.now() - event_time = now - timedelta(hours=1) - remix_time = now - timedelta(minutes=30) - REMIXER_ID = 4 - - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": REMIXER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - }, - # Two remixes by the same user - { - "track_id": 300, - "owner_id": REMIXER_ID, - "is_current": True, - "is_delete": False, - "created_at": remix_time, - "updated_at": remix_time, - "remix_of": {"tracks": [{"parent_track_id": TEST_TRACK_ID}]}, - }, - { - "track_id": 301, - "owner_id": REMIXER_ID, - "is_current": True, - "is_delete": False, - "created_at": remix_time, - "updated_at": remix_time, - "remix_of": {"tracks": [{"parent_track_id": TEST_TRACK_ID}]}, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "end_date": event_time, - "created_at": event_time, - "updated_at": event_time, - } - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ended_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDED) - .all() - ) - notif_count = 0 - for notification in notifications: - if REMIXER_ID in notification.user_ids: - notif_count += 1 - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_ENDED - assert notification.group_id.startswith("fan_remix_contest_ended:") - assert notif_count == 1 - - -def test_fan_remix_contest_ended_notification_private_parent_track(app): - """Test that no notification is created for remixers if the parent track is private (unlisted)""" - with app.app_context(): - db = get_db() - - now = datetime.now() - event_time = now - timedelta(hours=1) - remix_time = now - timedelta(minutes=30) - PRIVATE_TRACK_ID = 400 - PRIVATE_TRACK_OWNER_ID = 10 - REMIXER_ID = 11 - - entities = { - "users": [ - { - "user_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": REMIXER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": PRIVATE_TRACK_ID, - "owner_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Mark as private - "created_at": now, - "updated_at": now, - }, - # Remix by REMIXER_ID - { - "track_id": 401, - "owner_id": REMIXER_ID, - "is_current": True, - "is_delete": False, - "created_at": remix_time, - "updated_at": remix_time, - "remix_of": {"tracks": [{"parent_track_id": PRIVATE_TRACK_ID}]}, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": PRIVATE_TRACK_OWNER_ID, - "entity_id": PRIVATE_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "end_date": event_time, - "created_at": event_time, - "updated_at": event_time, - } - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ended_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDED) - .all() - ) - # Should not notify any remixers for private parent tracks - assert len(notifications) == 0 diff --git a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ending_soon_notification.py b/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ending_soon_notification.py deleted file mode 100644 index 8b904faa3f9..00000000000 --- a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_ending_soon_notification.py +++ /dev/null @@ -1,274 +0,0 @@ -import logging -from datetime import datetime, timedelta - -from integration_tests.utils import populate_mock_db -from src.models.notifications.notification import Notification -from src.queries.get_notifications import NotificationType -from src.tasks.remix_contest_notifications.fan_remix_contest_ending_soon import ( - create_fan_remix_contest_ending_soon_notifications, -) -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -TEST_EVENT_CREATOR_ID = 1 -TEST_TRACK_ID = 100 -TEST_FOLLOWER_ID = 2 -TEST_FAVORITER_ID = 3 - - -def test_fan_remix_contest_ending_soon_notification_for_followers_and_favoriters(app): - """Test that remix contest ending soon notification is created for followers and users who favorited the track""" - with app.app_context(): - db = get_db() - - now = datetime.now() - end_date = now + timedelta(hours=48) - - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - }, - ], - "follows": [ - { - "follower_user_id": TEST_FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "saves": [ - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": end_date, - }, - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ending_soon_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDING_SOON) - .all() - ) - notified_user_ids = set() - for notification in notifications: - notified_user_ids.update(notification.user_ids) - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_ENDING_SOON - assert notification.group_id.startswith("fan_remix_contest_ending_soon:") - # Should notify both the follower and the favoriter - assert TEST_FOLLOWER_ID in notified_user_ids - assert TEST_FAVORITER_ID in notified_user_ids - assert len(notified_user_ids) == 2 - - -def test_fan_remix_contest_ending_soon_notification_no_duplicate_for_follower_and_favoriter( - app, -): - """Test that a user who both follows the creator and saved the track only gets one notification""" - with app.app_context(): - db = get_db() - - now = datetime.now() - BOTH_ID = 4 - end_date = now + timedelta(hours=48) - - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": BOTH_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - }, - ], - "follows": [ - { - "follower_user_id": BOTH_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "saves": [ - { - "user_id": BOTH_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": end_date, - }, - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ending_soon_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDING_SOON) - .all() - ) - notif_count = 0 - for notification in notifications: - if BOTH_ID in notification.user_ids: - notif_count += 1 - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert ( - notification.type == NotificationType.FAN_REMIX_CONTEST_ENDING_SOON - ) - assert notification.group_id.startswith( - "fan_remix_contest_ending_soon:" - ) - assert notif_count == 1 - - -def test_fan_remix_contest_ending_soon_notification_private_track(app): - """Test that no notification is created for a remix contest on a private (unlisted) track""" - with app.app_context(): - db = get_db() - - now = datetime.now() - end_date = now + timedelta(hours=48) - PRIVATE_TRACK_ID = 200 - PRIVATE_TRACK_OWNER_ID = 5 - PRIVATE_TRACK_FOLLOWER_ID = 6 - - entities = { - "users": [ - { - "user_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": PRIVATE_TRACK_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": PRIVATE_TRACK_ID, - "owner_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Mark as private - "created_at": now, - "updated_at": now, - }, - ], - "follows": [ - { - "follower_user_id": PRIVATE_TRACK_FOLLOWER_ID, - "followee_user_id": PRIVATE_TRACK_OWNER_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": PRIVATE_TRACK_OWNER_ID, - "entity_id": PRIVATE_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - "end_date": end_date, - }, - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - create_fan_remix_contest_ending_soon_notifications(session) - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_ENDING_SOON) - .all() - ) - # Should not notify anyone for private tracks - assert len(notifications) == 0 diff --git a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_started_notification.py b/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_started_notification.py deleted file mode 100644 index 9110e385df0..00000000000 --- a/packages/discovery-provider/integration_tests/tasks/test_fan_remix_contest_started_notification.py +++ /dev/null @@ -1,1068 +0,0 @@ -import json -import logging -from datetime import datetime, timedelta - -from web3 import Web3 -from web3.datastructures import AttributeDict - -from integration_tests.challenges.index_helpers import UpdateTask -from integration_tests.utils import populate_mock_db -from src.challenges.challenge_event_bus import ChallengeEventBus, setup_challenge_bus -from src.models.notifications.notification import Notification -from src.models.tracks.track import Track -from src.queries.get_notifications import NotificationType -from src.tasks.entity_manager.entity_manager import entity_manager_update -from src.tasks.publish_scheduled_releases import _publish_scheduled_releases -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - -TEST_EVENT_CREATOR_ID = 1 -TEST_TRACK_ID = 100 -TEST_PRIVATE_TRACK_ID = 101 -TEST_FOLLOWER_ID = 2 -TEST_FAVORITER_ID = 3 - -default_metadata = { - "cover_art": None, - "cover_art_sizes": "QmdxhDiRUC3zQEKqwnqksaSsSSeHiRghjwKzwoRvm77yaZ", - "tags": "realmagic,theroom", - "genre": "R&B/Soul", - "mood": "Empowering", - "credits_splits": None, - "created_at": "2020-07-11 08:22:15", - "create_date": None, - "updated_at": "2020-07-11 08:22:15", - "release_date": "2020-07-11 08:22:15", - "file_type": None, - "is_playlist_upload": True, - "track_segments": [ - { - "duration": 6.016, - "multihash": "QmabM5svgDgcRdQZaEKSMBCpSZrrYy2y87L8Dx8EQ3T2jp", - } - ], - "has_current_user_reposted": False, - "is_current": True, - "field_visibility": { - "mood": True, - "tags": True, - "genre": True, - "share": True, - "play_count": True, - "remixes": True, - }, - "remix_of": None, - "repost_count": 12, - "save_count": 21, - "description": "some description", - "license": "All rights reserved", - "isrc": None, - "iswc": None, - "stem_of": None, - "ai_attribution_user_id": None, - "orig_file_cid": "original-file-cid", - "orig_filename": "original-filename", - "is_original_available": False, -} - - -def test_fan_remix_contest_started_notification_for_followers_and_favoriters(app): - """Test that remix contest started notification is created for followers and users who favorited the track, - but not for private tracks even if they have followers/favoriters. See handle_event.sql for the logic. - """ - with app.app_context(): - db = get_db() - - now = datetime.now() - - # First: insert all entities except events - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": False, - "created_at": now, - "updated_at": now, - }, - { - "track_id": TEST_PRIVATE_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Private track - "created_at": now, - "updated_at": now, - }, - ], - "follows": [ - { - "follower_user_id": TEST_FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_PRIVATE_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - }, - ], - # no "events" - } - populate_mock_db(db, entities) - - # Second: insert events for both tracks - event_entities = { - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - }, - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_PRIVATE_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - }, - ] - } - populate_mock_db(db, event_entities) - - with db.scoped_session() as session: - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - notified_user_ids = set() - for notification in notifications: - notified_user_ids.update(notification.user_ids) - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.group_id.startswith("fan_remix_contest_started:") - # Ensure no notifications were created for the private track - assert notification.data["entity_id"] != TEST_PRIVATE_TRACK_ID - # Should notify both the follower and the favoriter - assert TEST_FOLLOWER_ID in notified_user_ids - assert TEST_FAVORITER_ID in notified_user_ids - assert len(notified_user_ids) == 2 - - -def test_fan_remix_contest_started_notification_no_duplicate_for_follower_and_favoriter( - app, -): - """Test that a user who both follows the creator and saved the track only gets one notification""" - with app.app_context(): - db = get_db() - - now = datetime.now() - BOTH_ID = 4 - - # First: insert all entities except events - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": BOTH_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - "updated_at": now, - } - ], - "follows": [ - { - "follower_user_id": BOTH_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": BOTH_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - # no "events" - } - populate_mock_db(db, entities) - - # Second: insert just the event - event_entities = { - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ] - } - populate_mock_db(db, event_entities) - - with db.scoped_session() as session: - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - # There should be only one notification for BOTH_ID - notif_count = 0 - for notification in notifications: - if BOTH_ID in notification.user_ids: - notif_count += 1 - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.specifier == str(BOTH_ID) - assert notification.group_id.startswith("fan_remix_contest_started:") - assert notif_count == 1 - - -def test_fan_remix_contest_started_notification_on_track_update(app, mocker): - """Test that notifications are created when a private track with a remix contest becomes public via update""" - with app.app_context(): - db = get_db() - web3 = Web3() - challenge_event_bus: ChallengeEventBus = setup_challenge_bus() - update_task = UpdateTask(web3, challenge_event_bus) - - now = datetime.now() - - # First: insert all entities including a private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - "handle": "creator", - "wallet": "creator_wallet", - }, - { - "user_id": TEST_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "created_at": now, - "updated_at": now, - } - ], - "follows": [ - { - "follower_user_id": TEST_FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - } - populate_mock_db(db, entities) - - # Create update metadata to make track public - update_track_metadata = { - **default_metadata, - "owner_id": TEST_EVENT_CREATOR_ID, - "track_id": TEST_TRACK_ID, - "title": "Public Track NEW NEW NEW", - "is_unlisted": False, # Make track public - } - - update_track_json = json.dumps(update_track_metadata) - update_track_receipt = [ - { - "args": AttributeDict( - { - "_entityId": TEST_TRACK_ID, - "_entityType": "Track", - "_userId": TEST_EVENT_CREATOR_ID, - "_action": "Update", - "_metadata": f'{{"cid": "QmUpdateTrack", "data": {update_track_json}}}', - "_signer": "creator_wallet", - } - ) - } - ] - - def get_events_side_effect(_, tx_receipt): - return update_track_receipt - - mocker.patch( - "src.tasks.entity_manager.entity_manager.get_entity_manager_events_tx", - side_effect=get_events_side_effect, - autospec=True, - ) - - # Verify no notifications exist before update - with db.scoped_session() as session: - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - assert len(notifications) == 0 - - # Process the update that makes the track public - with db.scoped_session() as session: - entity_manager_update( - update_task, - session, - entity_manager_txs=[ - AttributeDict({"transactionHash": web3.to_bytes(text="UpdateTrack")}) - ], - block_number=1, - block_timestamp=int(now.timestamp()), - block_hash=hex(1), - ) - - # Verify notifications were created after update - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - notified_user_ids = set() - for notification in notifications: - notified_user_ids.update(notification.user_ids) - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.group_id.startswith("fan_remix_contest_started:") - - # Should notify both the follower and the favoriter - assert TEST_FOLLOWER_ID in notified_user_ids - assert TEST_FAVORITER_ID in notified_user_ids - assert len(notified_user_ids) == 2 - - -def test_fan_remix_contest_started_notification_on_scheduled_release(app): - """Test that notifications are created when a scheduled track with a remix contest becomes public""" - with app.app_context(): - db = get_db() - - now = datetime.now() - past_release_date = now - timedelta( - days=1 - ) # Release date in the past to trigger publish - - # First: insert all entities including a scheduled private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": TEST_FAVORITER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "is_scheduled_release": True, # This is a scheduled release - "release_date": past_release_date, # Release date in the past - "created_at": now, - "updated_at": now, - "blocknumber": 1, - } - ], - "follows": [ - { - "follower_user_id": TEST_FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": TEST_FAVORITER_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - } - populate_mock_db(db, entities) - - # Verify no notifications exist before publishing - with db.scoped_session() as session: - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - assert len(notifications) == 0 - - # Run the scheduled release publish - _publish_scheduled_releases(session) - - # Verify track was made public - track = session.query(Track).filter_by(track_id=TEST_TRACK_ID).first() - assert track.is_unlisted == False - - # Verify notifications were created - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - notified_user_ids = set() - for notification in notifications: - notified_user_ids.update(notification.user_ids) - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.group_id.startswith("fan_remix_contest_started:") - - # Should notify both the follower and the favoriter - assert TEST_FOLLOWER_ID in notified_user_ids - assert TEST_FAVORITER_ID in notified_user_ids - assert len(notified_user_ids) == 2 - - -def test_fan_remix_contest_started_notification_no_duplicate_on_track_update( - app, mocker -): - """Test that a user who both follows the creator and saved the track only gets one notification - when a private track becomes public via update""" - with app.app_context(): - db = get_db() - web3 = Web3() - challenge_event_bus: ChallengeEventBus = setup_challenge_bus() - update_task = UpdateTask(web3, challenge_event_bus) - - now = datetime.now() - BOTH_ID = 4 - - # First: insert all entities including a private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - "handle": "creator", - "wallet": "creator_wallet", - }, - { - "user_id": BOTH_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "created_at": now, - "updated_at": now, - } - ], - "follows": [ - { - "follower_user_id": BOTH_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": BOTH_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - } - populate_mock_db(db, entities) - - # Create update metadata to make track public - update_track_metadata = { - **default_metadata, - "owner_id": TEST_EVENT_CREATOR_ID, - "track_id": TEST_TRACK_ID, - "title": "Public Track NEW NEW NEW", - "is_unlisted": False, # Make track public - } - - update_track_json = json.dumps(update_track_metadata) - update_track_receipt = [ - { - "args": AttributeDict( - { - "_entityId": TEST_TRACK_ID, - "_entityType": "Track", - "_userId": TEST_EVENT_CREATOR_ID, - "_action": "Update", - "_metadata": f'{{"cid": "QmUpdateTrack", "data": {update_track_json}}}', - "_signer": "creator_wallet", - } - ) - } - ] - - def get_events_side_effect(_, tx_receipt): - return update_track_receipt - - mocker.patch( - "src.tasks.entity_manager.entity_manager.get_entity_manager_events_tx", - side_effect=get_events_side_effect, - autospec=True, - ) - - # Process the update that makes the track public - with db.scoped_session() as session: - entity_manager_update( - update_task, - session, - entity_manager_txs=[ - AttributeDict({"transactionHash": web3.to_bytes(text="UpdateTrack")}) - ], - block_number=1, - block_timestamp=int(now.timestamp()), - block_hash=hex(1), - ) - - # Verify notifications after update - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - # There should be only one notification for BOTH_ID - notif_count = 0 - for notification in notifications: - if BOTH_ID in notification.user_ids: - notif_count += 1 - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.specifier == str(BOTH_ID) - assert notification.group_id.startswith("fan_remix_contest_started:") - assert notif_count == 1 - - -def test_fan_remix_contest_started_notification_no_duplicate_on_scheduled_release(app): - """Test that a user who both follows the creator and saved the track only gets one notification - when a scheduled private track becomes public""" - with app.app_context(): - db = get_db() - - now = datetime.now() - past_release_date = now - timedelta( - days=1 - ) # Release date in the past to trigger publish - BOTH_ID = 4 - - # First: insert all entities including a scheduled private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": BOTH_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "is_scheduled_release": True, # This is a scheduled release - "release_date": past_release_date, # Release date in the past - "created_at": now, - "updated_at": now, - "blocknumber": 1, - } - ], - "follows": [ - { - "follower_user_id": BOTH_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "saves": [ - { - "user_id": BOTH_ID, - "save_item_id": TEST_TRACK_ID, - "save_type": "track", - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - # Run the scheduled release publish - _publish_scheduled_releases(session) - - # Verify track was made public - track = session.query(Track).filter_by(track_id=TEST_TRACK_ID).first() - assert track.is_unlisted == False - - # Verify notifications - notifications = ( - session.query(Notification) - .filter(Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED) - .all() - ) - # There should be only one notification for BOTH_ID - notif_count = 0 - for notification in notifications: - if BOTH_ID in notification.user_ids: - notif_count += 1 - assert notification.data["entity_user_id"] == TEST_EVENT_CREATOR_ID - assert notification.data["entity_id"] == TEST_TRACK_ID - assert notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED - assert notification.specifier == str(BOTH_ID) - assert notification.group_id.startswith("fan_remix_contest_started:") - assert notif_count == 1 - - -def test_fan_remix_contest_started_notification_no_duplicate_with_existing_on_track_update( - app, mocker -): - """Test that we don't create duplicate notifications when making a track public via update - if an identical notification already exists""" - with app.app_context(): - db = get_db() - web3 = Web3() - challenge_event_bus: ChallengeEventBus = setup_challenge_bus() - update_task = UpdateTask(web3, challenge_event_bus) - - now = datetime.now() - FOLLOWER_ID = 4 - - # First: insert all entities including a private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - "handle": "creator", - "wallet": "creator_wallet", - }, - { - "user_id": FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "created_at": now, - "updated_at": now, - "blocknumber": 1, - } - ], - "follows": [ - { - "follower_user_id": FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - # Add an existing notification - "notification": [ - { - "blocknumber": 1, - "user_ids": [FOLLOWER_ID], - "timestamp": now, - "type": NotificationType.FAN_REMIX_CONTEST_STARTED, - "specifier": str(FOLLOWER_ID), - "group_id": f"fan_remix_contest_started:{TEST_TRACK_ID}:user:{TEST_EVENT_CREATOR_ID}", - "data": { - "entity_user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - }, - } - ], - } - populate_mock_db(db, entities) - - # Create update metadata to make track public - update_track_metadata = { - **default_metadata, - "owner_id": TEST_EVENT_CREATOR_ID, - "track_id": TEST_TRACK_ID, - "title": "Public Track NEW NEW NEW", - "is_unlisted": False, # Make track public - } - - update_track_json = json.dumps(update_track_metadata) - update_track_receipt = [ - { - "args": AttributeDict( - { - "_entityId": TEST_TRACK_ID, - "_entityType": "Track", - "_userId": TEST_EVENT_CREATOR_ID, - "_action": "Update", - "_metadata": f'{{"cid": "QmUpdateTrack", "data": {update_track_json}}}', - "_signer": "creator_wallet", - } - ) - } - ] - - def get_events_side_effect(_, tx_receipt): - return update_track_receipt - - mocker.patch( - "src.tasks.entity_manager.entity_manager.get_entity_manager_events_tx", - side_effect=get_events_side_effect, - autospec=True, - ) - - # Process the update that makes the track public - with db.scoped_session() as session: - # Count notifications before update - notifications_before = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED, - ) - .all() - ) - assert len(notifications_before) == 1 - assert notifications_before[0].user_ids == [FOLLOWER_ID] - - entity_manager_update( - update_task, - session, - entity_manager_txs=[ - AttributeDict({"transactionHash": web3.to_bytes(text="UpdateTrack")}) - ], - block_number=1, - block_timestamp=int(now.timestamp()), - block_hash=hex(1), - ) - - # Verify no new notifications were created - notifications_after = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED, - ) - .all() - ) - assert len(notifications_after) == 1 - assert notifications_after[0].user_ids == [FOLLOWER_ID] - - -def test_fan_remix_contest_started_notification_no_duplicate_with_existing_on_scheduled_release( - app, -): - """Test that we don't create duplicate notifications when making a track public via scheduled release - if an identical notification already exists""" - with app.app_context(): - db = get_db() - - now = datetime.now() - past_release_date = now - timedelta( - days=1 - ) # Release date in the past to trigger publish - FOLLOWER_ID = 4 - - # First: insert all entities including a scheduled private track - entities = { - "users": [ - { - "user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - { - "user_id": FOLLOWER_ID, - "is_current": True, - "created_at": now, - "updated_at": now, - }, - ], - "tracks": [ - { - "track_id": TEST_TRACK_ID, - "owner_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "is_unlisted": True, # Track starts as private - "is_scheduled_release": True, # This is a scheduled release - "release_date": past_release_date, # Release date in the past - "created_at": now, - "updated_at": now, - "blocknumber": 1, - } - ], - "follows": [ - { - "follower_user_id": FOLLOWER_ID, - "followee_user_id": TEST_EVENT_CREATOR_ID, - "is_current": True, - "is_delete": False, - "created_at": now, - } - ], - "events": [ - { - "event_type": "remix_contest", - "user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - "entity_type": "track", - "is_deleted": False, - "created_at": now, - "updated_at": now, - } - ], - # Add an existing notification - "notification": [ - { - "blocknumber": 1, - "user_ids": [FOLLOWER_ID], - "timestamp": now, - "type": NotificationType.FAN_REMIX_CONTEST_STARTED, - "specifier": str(FOLLOWER_ID), - "group_id": f"fan_remix_contest_started:{TEST_TRACK_ID}:user:{TEST_EVENT_CREATOR_ID}", - "data": { - "entity_user_id": TEST_EVENT_CREATOR_ID, - "entity_id": TEST_TRACK_ID, - }, - } - ], - } - populate_mock_db(db, entities) - - with db.scoped_session() as session: - # Count notifications before publish - notifications_before = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED, - ) - .all() - ) - assert len(notifications_before) == 1 - assert notifications_before[0].user_ids == [FOLLOWER_ID] - - # Run the scheduled release publish - _publish_scheduled_releases(session) - - # Verify track was made public - track = session.query(Track).filter_by(track_id=TEST_TRACK_ID).first() - assert track.is_unlisted == False - - # Verify no new notifications were created - notifications_after = ( - session.query(Notification) - .filter( - Notification.type == NotificationType.FAN_REMIX_CONTEST_STARTED, - ) - .all() - ) - logger.info(f"notifications_after: {notifications_after}") - assert len(notifications_after) == 1 - assert notifications_after[0].user_ids == [FOLLOWER_ID] diff --git a/packages/discovery-provider/src/queries/get_aggregate_route_metrics.py b/packages/discovery-provider/src/queries/get_aggregate_route_metrics.py deleted file mode 100644 index 9d28f60f736..00000000000 --- a/packages/discovery-provider/src/queries/get_aggregate_route_metrics.py +++ /dev/null @@ -1,379 +0,0 @@ -import functools as ft -import logging -from datetime import date, timedelta - -from sqlalchemy import asc, func - -from src import exceptions -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -def get_aggregate_route_metrics(time_range, bucket_size): - """ - Returns a list of timestamps with unique count and total count for all routes - based on given time range and grouped by bucket size - - Returns: - [{ timestamp, unique_count, total_count }] - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_aggregate_route_metrics(session, time_range, bucket_size) - - -def _get_aggregate_route_metrics(session, time_range, bucket_size): - today = date.today() - seven_days_ago = today - timedelta(days=7) - thirty_days_ago = today - timedelta(days=30) - one_year_ago = today - timedelta(days=365) - first_day_of_month = today.replace(day=1) - - if time_range == "week": - if bucket_size == "day": - unique_counts = ( - session.query( - AggregateDailyUniqueUsersMetrics.timestamp, - AggregateDailyUniqueUsersMetrics.count, - AggregateDailyUniqueUsersMetrics.summed_count, - ) - .filter(seven_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateDailyTotalUsersMetrics.timestamp, - AggregateDailyTotalUsersMetrics.count, - ) - .filter(seven_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - if time_range == "month": - if bucket_size == "day": - unique_counts = ( - session.query( - AggregateDailyUniqueUsersMetrics.timestamp, - AggregateDailyUniqueUsersMetrics.count, - AggregateDailyUniqueUsersMetrics.summed_count, - ) - .filter(thirty_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateDailyTotalUsersMetrics.timestamp, - AggregateDailyTotalUsersMetrics.count, - ) - .filter(thirty_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - if bucket_size == "week": - unique_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyUniqueUsersMetrics.count).label("count"), - func.sum(AggregateDailyUniqueUsersMetrics.summed_count).label( - "summed_count" - ), - ) - .filter(thirty_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyTotalUsersMetrics.count).label("count"), - ) - .filter(thirty_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - if time_range == "year": - if bucket_size == "month": - unique_counts = ( - session.query( - AggregateMonthlyUniqueUsersMetric.timestamp, - AggregateMonthlyUniqueUsersMetric.count, - AggregateMonthlyUniqueUsersMetric.summed_count, - ) - .filter( - AggregateMonthlyUniqueUsersMetric.timestamp < first_day_of_month - ) - .filter(one_year_ago <= AggregateMonthlyUniqueUsersMetric.timestamp) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateMonthlyTotalUsersMetric.timestamp, - AggregateMonthlyTotalUsersMetric.count, - ) - .filter(AggregateMonthlyTotalUsersMetric.timestamp < first_day_of_month) - .filter(one_year_ago <= AggregateMonthlyTotalUsersMetric.timestamp) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - if time_range == "all_time": - if bucket_size == "month": - unique_counts = ( - session.query( - AggregateMonthlyUniqueUsersMetric.timestamp, - AggregateMonthlyUniqueUsersMetric.count, - AggregateMonthlyUniqueUsersMetric.summed_count, - ) - .filter( - AggregateMonthlyUniqueUsersMetric.timestamp < first_day_of_month - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateMonthlyTotalUsersMetric.timestamp, - AggregateMonthlyTotalUsersMetric.count, - ) - .filter(AggregateMonthlyTotalUsersMetric.timestamp < first_day_of_month) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - if bucket_size == "week": - unique_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyUniqueUsersMetrics.count).label("count"), - func.sum(AggregateDailyUniqueUsersMetrics.summed_count).label( - "summed_count" - ), - ) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyTotalUsersMetrics.count).label("count"), - ) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, counts in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - raise exceptions.ArgumentError("Invalid time_range") diff --git a/packages/discovery-provider/src/queries/get_app_name_metrics.py b/packages/discovery-provider/src/queries/get_app_name_metrics.py deleted file mode 100644 index b33a9252369..00000000000 --- a/packages/discovery-provider/src/queries/get_app_name_metrics.py +++ /dev/null @@ -1,171 +0,0 @@ -import logging -from datetime import date, timedelta - -from sqlalchemy import asc, desc, func - -from src import exceptions -from src.models.metrics.aggregate_daily_app_name_metrics import ( - AggregateDailyAppNameMetric, -) -from src.models.metrics.aggregate_monthly_app_name_metrics import ( - AggregateMonthlyAppNameMetric, -) -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -def get_historical_app_metrics(): - """ - Returns daily metrics for the last thirty days and all time monthly metrics - - Returns: - { - daily: { - 2021/01/15: {app1: ..., app2: ...} - ... - }, - monthly: { - 2021/01/01: {app1: ..., app2: ...} - ... - } - } - """ - - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_historical_app_metrics(session) - - -def _get_historical_app_metrics(session, min_count=100): - """ - gets historical app metrics monthly and daily request counts. - - Args: - session: Database session - min_count: Minimum count an app must have in order to be returned - """ - today = date.today() - thirty_days_ago = today - timedelta(days=30) - first_day_of_month = today.replace(day=1) - - daily_query = ( - session.query( - AggregateDailyAppNameMetric.timestamp, - AggregateDailyAppNameMetric.application_name, - AggregateDailyAppNameMetric.count, - ) - .filter(min_count <= AggregateDailyAppNameMetric.count) - .filter(thirty_days_ago <= AggregateDailyAppNameMetric.timestamp) - .filter(AggregateDailyAppNameMetric.timestamp < today) - .all() - ) - daily_metrics = {} - for attribute in daily_query: - day = str(attribute[0]) - if day not in daily_metrics: - daily_metrics[day] = {attribute[1]: attribute[2]} - else: - daily_metrics[day][attribute[1]] = attribute[2] - - monthly_query = ( - session.query( - AggregateMonthlyAppNameMetric.timestamp, - AggregateMonthlyAppNameMetric.application_name, - AggregateMonthlyAppNameMetric.count, - ) - .filter(min_count <= AggregateMonthlyAppNameMetric.count) - .filter(AggregateMonthlyAppNameMetric.timestamp < first_day_of_month) - .all() - ) - monthly_metrics = {} - for attribute in monthly_query: - month = str(attribute[0]) - if month not in monthly_metrics: - monthly_metrics[month] = {attribute[1]: attribute[2]} - else: - monthly_metrics[month][attribute[1]] = attribute[2] - - return {"daily": daily_metrics, "monthly": monthly_metrics} - - -def get_aggregate_app_metrics(time_range, limit): - """ - Returns app name metrics for a given time range - - Args: - time_range: one of "week", "month", "year", or "all_time" - limit: number The max number of apps to return - Returns: - [{ name: string, count: number }, ...] - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_aggregate_app_metrics(session, time_range, limit) - - -def _get_aggregate_app_metrics(session, time_range, limit): - today = date.today() - seven_days_ago = today - timedelta(days=7) - thirty_days_ago = today - timedelta(days=30) - one_year_ago = today - timedelta(days=365) - - if time_range == "week": - query = ( - session.query( - AggregateDailyAppNameMetric.application_name, - func.sum(AggregateDailyAppNameMetric.count).label("count"), - ) - .filter(seven_days_ago <= AggregateDailyAppNameMetric.timestamp) - .filter(AggregateDailyAppNameMetric.timestamp < today) - .group_by(AggregateDailyAppNameMetric.application_name) - .order_by(desc("count"), asc(AggregateDailyAppNameMetric.application_name)) - .limit(limit) - .all() - ) - elif time_range == "month": - query = ( - session.query( - AggregateDailyAppNameMetric.application_name, - func.sum(AggregateDailyAppNameMetric.count).label("count"), - ) - .filter(thirty_days_ago <= AggregateDailyAppNameMetric.timestamp) - .filter(AggregateDailyAppNameMetric.timestamp < today) - .group_by(AggregateDailyAppNameMetric.application_name) - .order_by(desc("count"), asc(AggregateDailyAppNameMetric.application_name)) - .limit(limit) - .all() - ) - elif time_range == "year": - query = ( - session.query( - AggregateMonthlyAppNameMetric.application_name, - func.sum(AggregateMonthlyAppNameMetric.count).label("count"), - ) - .filter(one_year_ago <= AggregateDailyAppNameMetric.timestamp) - .filter(AggregateMonthlyAppNameMetric.timestamp < today) - .group_by(AggregateMonthlyAppNameMetric.application_name) - .order_by( - desc("count"), asc(AggregateMonthlyAppNameMetric.application_name) - ) - .limit(limit) - .all() - ) - elif time_range == "all_time": - query = ( - session.query( - AggregateMonthlyAppNameMetric.application_name, - func.sum(AggregateMonthlyAppNameMetric.count).label("count"), - ) - .filter(AggregateMonthlyAppNameMetric.timestamp < today) - .group_by(AggregateMonthlyAppNameMetric.application_name) - .order_by( - desc("count"), asc(AggregateMonthlyAppNameMetric.application_name) - ) - .limit(limit) - .all() - ) - else: - raise exceptions.ArgumentError("Invalid time_range") - - return [{"name": item[0], "count": item[1]} for item in query] diff --git a/packages/discovery-provider/src/queries/get_attestation.py b/packages/discovery-provider/src/queries/get_attestation.py deleted file mode 100644 index 3156b825959..00000000000 --- a/packages/discovery-provider/src/queries/get_attestation.py +++ /dev/null @@ -1,257 +0,0 @@ -from datetime import datetime -from typing import Tuple - -import pytz -from eth_keys import keys -from eth_utils.conversions import to_bytes -from hexbytes import HexBytes -from solders.pubkey import Pubkey -from sqlalchemy.orm.session import Session -from sqlalchemy.sql.elements import and_ -from web3 import Web3 - -from src.models.rewards.challenge import Challenge -from src.models.rewards.challenge_disbursement import ChallengeDisbursement -from src.models.rewards.user_challenge import UserChallenge -from src.models.users.user import User -from src.queries.get_disbursed_challenges_amount import ( - get_disbursed_challenges_amount, - get_weekly_pool_window_start, -) -from src.solana.constants import WAUDIO_DECIMALS -from src.tasks.index_oracles import ( - get_oracle_addresses_from_chain, - oracle_addresses_key, -) -from src.utils.config import shared_config -from src.utils.get_all_nodes import ( - get_all_content_nodes_cached, - get_all_discovery_nodes_cached, -) -from src.utils.redis_connection import get_redis - -REWARDS_MANAGER_ACCOUNT = shared_config["solana"]["rewards_manager_account"] -REWARDS_MANAGER_ACCOUNT_PUBLIC_KEY = None -if REWARDS_MANAGER_ACCOUNT: - REWARDS_MANAGER_ACCOUNT_PUBLIC_KEY = Pubkey.from_string(REWARDS_MANAGER_ACCOUNT) -DATETIME_FORMAT_STRING = "%Y-%m-%d %H:%M:%S.%f+00" - - -class Attestation: - """Represents DN attesting to a user completing a given challenge""" - - def __init__( - self, - *, - amount: str, - oracle_address: str, - user_address: str, - challenge_id: str, - challenge_specifier: str, - ): - self.amount = amount - self.oracle_address = oracle_address - self.challenge_id = challenge_id - self.user_address = user_address - self.challenge_specifier = challenge_specifier - - def __repr__(self): - # Format: - # recipient + "_" + amount + "_" + ID (challengeId + specifier) + "_" + oracle_address - return "_".join( - [ - self.user_address, - self.amount, - self._get_combined_id(), - self.oracle_address, - ] - ) - - def _get_combined_id(self): - return f"{self.challenge_id}:{self.challenge_specifier}" - - def _get_encoded_amount(self): - amt = int(self.amount) * 10**WAUDIO_DECIMALS - return amt.to_bytes(8, byteorder="little") - - def get_attestation_bytes(self): - user_bytes = to_bytes(hexstr=self.user_address) - oracle_bytes = to_bytes(hexstr=self.oracle_address) - combined_id_bytes = to_bytes(text=self._get_combined_id()) - amount_bytes = self._get_encoded_amount() - items = [user_bytes, amount_bytes, combined_id_bytes, oracle_bytes] - joined = to_bytes(text="_").join(items) - return joined - - -# Define custom error strings -CHALLENGE_INCOMPLETE = "CHALLENGE_INCOMPLETE" -ALREADY_DISBURSED = "ALREADY_DISBURSED" -INVALID_ORACLE = "INVALID_ORACLE" -MISSING_CHALLENGES = "MISSING_CHALLENGES" -INVALID_INPUT = "INVALID_INPUT" -USER_NOT_FOUND = "USER_NOT_FOUND" -POOL_EXHAUSTED = "POOL_EXHAUSTED" -WAIT_FOR_COOLDOWN = "WAIT_FOR_COOLDOWN" - - -class AttestationError(Exception): - pass - - -def is_valid_oracle(address: str) -> bool: - redis = get_redis() - oracle_addresses = redis.get(oracle_addresses_key) - if oracle_addresses: - oracle_addresses = oracle_addresses.decode().split(",") - else: - oracle_addresses = get_oracle_addresses_from_chain(redis) - return address in oracle_addresses - - -def sign_attestation(attestation_bytes: bytes, private_key: str): - k = keys.PrivateKey(HexBytes(private_key)) - to_sign_hash = Web3.keccak(attestation_bytes) - sig = k.sign_msg_hash(to_sign_hash) - return sig.to_hex() - - -def get_attestation( - session: Session, - *, - challenge_id: str, - user_id: int, - oracle_address: str, - specifier: str, -): - """ - Returns a delegate_owner_wallet, signed_attestation tuple, - or throws an error explaining why the attestation was - not able to be created. - """ - if not user_id or not challenge_id or not oracle_address: - raise AttestationError(INVALID_INPUT) - - # First, validate the oracle adddress - if not is_valid_oracle(oracle_address): - raise AttestationError(INVALID_ORACLE) - - challenges_and_disbursements: Tuple[ - UserChallenge, Challenge, ChallengeDisbursement - ] = ( - session.query(UserChallenge, Challenge, ChallengeDisbursement) - .join(Challenge, Challenge.id == UserChallenge.challenge_id) - # Need to do outerjoin because some challenges - # may not have disbursements - .outerjoin( - ChallengeDisbursement, - and_( - ChallengeDisbursement.specifier == UserChallenge.specifier, - ChallengeDisbursement.challenge_id == UserChallenge.challenge_id, - ), - ) - .filter( - UserChallenge.user_id == user_id, - UserChallenge.challenge_id == challenge_id, - UserChallenge.specifier == specifier, - ) - ).one_or_none() - - if not challenges_and_disbursements: - raise AttestationError(MISSING_CHALLENGES) - user_challenge, challenge, disbursement = ( - challenges_and_disbursements[0], - challenges_and_disbursements[1], - challenges_and_disbursements[2], - ) - if not user_challenge.is_complete: - raise AttestationError(CHALLENGE_INCOMPLETE) - if disbursement: - raise AttestationError(ALREADY_DISBURSED) - now_utc = datetime.now(pytz.UTC) - if challenge.cooldown_days and user_challenge.completed_at: - time_passed = now_utc - pytz.utc.localize(user_challenge.completed_at) - if time_passed.days < challenge.cooldown_days: - raise AttestationError(WAIT_FOR_COOLDOWN) - if challenge.weekly_pool: - disbursed_amount = get_disbursed_challenges_amount( - session, challenge.id, get_weekly_pool_window_start(now_utc) - ) - if disbursed_amount + user_challenge.amount > challenge.weekly_pool: - raise AttestationError(POOL_EXHAUSTED) - - # Get the users's eth address - user_eth_address = ( - session.query(User.wallet) - .filter( - User.is_current == True, - User.user_id == user_id, - User.is_deactivated == False, - ) - .one_or_none() - ) - if not user_eth_address: - raise AttestationError(USER_NOT_FOUND) - user_address = str(user_eth_address[0]) - - attestation = Attestation( - amount=str(user_challenge.amount), - oracle_address=oracle_address, - user_address=user_address, - challenge_id=challenge.id, - challenge_specifier=user_challenge.specifier, - ) - - attestation_bytes = attestation.get_attestation_bytes() - signed_attestation: str = sign_attestation( - attestation_bytes, shared_config["delegate"]["private_key"] - ) - return ( - shared_config["delegate"]["owner_wallet"], - signed_attestation, - ) - - -ADD_SENDER_MESSAGE_PREFIX = "add" - - -def verify_discovery_node_exists_on_chain(new_sender_address: str) -> bool: - redis = get_redis() - nodes = get_all_discovery_nodes_cached(redis) - wallets = set([d["delegateOwnerWallet"] for d in nodes] if nodes else []) - return new_sender_address in wallets - - -def verify_content_node_exists_on_chain(new_sender_address: str) -> bool: - redis = get_redis() - nodes = get_all_content_nodes_cached(redis) - wallets = set([d["delegateOwnerWallet"] for d in nodes] if nodes else []) - return new_sender_address in wallets - - -def get_create_sender_attestation(new_sender_address: str) -> Tuple[str, str]: - """ - Returns a owner_wallet, signed_attestation tuple, - or throws an error explaining why the sender attestation was - not able to be created. - """ - if not REWARDS_MANAGER_ACCOUNT_PUBLIC_KEY: - raise Exception("No Rewards Manager Account initialized") - - is_valid_dn = verify_discovery_node_exists_on_chain(new_sender_address) - is_valid_cn = verify_content_node_exists_on_chain(new_sender_address) - if not is_valid_dn and not is_valid_cn: - raise Exception(f"Expected {new_sender_address} to be registered on chain") - - items = [ - to_bytes(text=ADD_SENDER_MESSAGE_PREFIX), - # Solana PubicKey should be coerced to bytes using the pythonic bytes - bytes(REWARDS_MANAGER_ACCOUNT_PUBLIC_KEY), - to_bytes(hexstr=new_sender_address), - ] - attestation_bytes = to_bytes(text="").join(items) - signed_attestation: str = sign_attestation( - attestation_bytes, shared_config["delegate"]["private_key"] - ) - owner_wallet = shared_config["delegate"]["owner_wallet"] - return owner_wallet, signed_attestation diff --git a/packages/discovery-provider/src/queries/get_challenges.py b/packages/discovery-provider/src/queries/get_challenges.py deleted file mode 100644 index df9df897b2e..00000000000 --- a/packages/discovery-provider/src/queries/get_challenges.py +++ /dev/null @@ -1,314 +0,0 @@ -from collections import defaultdict -from functools import reduce -from typing import DefaultDict, Dict, List, Optional, Tuple, TypedDict, cast - -from sqlalchemy import and_ -from sqlalchemy.orm import Session - -from src.challenges.challenge_event_bus import ChallengeEventBus -from src.challenges.listen_streak_endless_challenge import NUM_DAYS_IN_STREAK -from src.models.rewards.challenge import Challenge, ChallengeType -from src.models.rewards.challenge_disbursement import ChallengeDisbursement -from src.models.rewards.user_challenge import UserChallenge -from src.utils.spl_audio import from_lamports - - -class ChallengeResponse(TypedDict): - challenge_id: str - user_id: int - specifier: str - is_complete: bool - is_active: bool # need this - is_disbursed: bool # need this - current_step_count: Optional[int] - max_steps: Optional[int] - challenge_type: str - amount: str - disbursed_amount: int - cooldown_days: Optional[int] - metadata: Dict - - -def get_disbursements_by_challenge_id( - disbursements: List[ChallengeDisbursement], challenge_id: str -) -> List[ChallengeDisbursement]: - return list(filter(lambda d: d.challenge_id == challenge_id, disbursements)) - - -def get_disbursed_amount(disbursements: List[ChallengeDisbursement]) -> int: - if disbursements is None: - return 0 - return sum( - from_lamports(disbursement.amount) - for disbursement in filter(lambda x: x is not None, disbursements) - ) - - -def rollup_aggregates( - event_bus: ChallengeEventBus, - session: Session, - user_challenges: List[UserChallenge], - parent_challenge: Challenge, - disbursements: List[ChallengeDisbursement], -) -> ChallengeResponse: - step_count = parent_challenge.step_count - num_complete = reduce( - lambda acc, cur: cast(int, acc) + cur.amount if cur.is_complete else acc, - user_challenges, - 0, - ) - amount = parent_challenge.amount - current_step_count = num_complete - - # The parent challenge should have a step count, otherwise, we can just - # say it's complete. - if parent_challenge.id == "o": - # one shot is a special aggregate that's different for every user - # max steps is unique for user so override the parent challenge step count - # each step has a value of 1 - step_count = sum(challenge.amount for challenge in user_challenges) - amount = "1" - is_complete = True - elif parent_challenge.id == "e": - # `step_count` should only reflect the _current_ in-progress streak, so we - # count only the `step_count` of the most recent "full" 7-day streak user_challenge - # row, and sum that with the 1-amount user_challenge rows after that one (if any). - sorted_challenges = sorted( - user_challenges, key=lambda x: x.created_at, reverse=True - ) - most_recent_challenge = sorted_challenges[0] - if most_recent_challenge.is_complete: - current_step_count = 0 - for challenge in sorted_challenges: - if challenge.current_step_count is None: - continue - current_step_count += challenge.current_step_count - if challenge.current_step_count >= NUM_DAYS_IN_STREAK: - break - else: - current_step_count = most_recent_challenge.current_step_count or 0 - is_complete = num_complete >= NUM_DAYS_IN_STREAK - elif parent_challenge.step_count: - is_complete = num_complete >= parent_challenge.step_count - else: - is_complete = True - override_step_count = event_bus.get_manager( - parent_challenge.id - ).get_override_challenge_step_count(session, user_challenges[0].user_id) - if override_step_count is not None: - current_step_count = override_step_count - - response_dict: ChallengeResponse = { - "challenge_id": parent_challenge.id, - "user_id": user_challenges[0].user_id, - "specifier": "", - "is_complete": is_complete, - "current_step_count": current_step_count, - "max_steps": step_count, - "challenge_type": parent_challenge.type, - "is_active": parent_challenge.active, - "is_disbursed": False, # This doesn't indicate anything for aggregate challenges - "amount": amount, - "disbursed_amount": get_disbursed_amount(disbursements), - "cooldown_days": parent_challenge.cooldown_days or 0, - "metadata": {}, - } - return response_dict - - -def to_challenge_response( - user_challenge: UserChallenge, - challenge: Challenge, - disbursements: List[ChallengeDisbursement], - metadata: Dict, -) -> ChallengeResponse: - return { - "challenge_id": challenge.id, - "user_id": user_challenge.user_id, - "specifier": user_challenge.specifier, - "is_complete": user_challenge.is_complete, - "current_step_count": user_challenge.current_step_count, - "max_steps": challenge.step_count, - "challenge_type": challenge.type, - "is_active": challenge.active, - "is_disbursed": disbursements is not None and len(disbursements) > 0, - "amount": challenge.amount, - "disbursed_amount": get_disbursed_amount(disbursements), - "cooldown_days": challenge.cooldown_days or 0, - "metadata": metadata, - } - - -def create_empty_user_challenges( - user_id: int, challenges: List[Challenge], metadatas: List[Dict] -) -> List[ChallengeResponse]: - user_challenges: List[ChallengeResponse] = [] - for i, challenge in enumerate(challenges): - user_challenge: ChallengeResponse = { - "challenge_id": challenge.id, - "user_id": user_id, - "specifier": "", - "is_complete": False, - "current_step_count": 0, - "max_steps": challenge.step_count, - "challenge_type": challenge.type, - "is_active": challenge.active, - "is_disbursed": False, - "amount": challenge.amount, - "disbursed_amount": 0, - "cooldown_days": challenge.cooldown_days or 0, - "metadata": metadatas[i], - } - user_challenges.append(user_challenge) - return user_challenges - - -def get_challenges_metadata( - session: Session, - event_bus: ChallengeEventBus, - challenges: List[UserChallenge], -) -> List[Dict]: - # Break it up into map per challenge type - challenge_map: Dict[str, List[str]] = defaultdict(lambda: []) - specifier_metadata_map: Dict[str, Dict] = {} - for challenge in challenges: - challenge_map[challenge.challenge_id].append(challenge.specifier) - - for challenge_type, specifiers in challenge_map.items(): - manager = event_bus.get_manager(challenge_type) - metadatas = manager.get_metadata(session, specifiers) - for i, specifier in enumerate(specifiers): - metadata = metadatas[i] - specifier_metadata_map[specifier] = metadata - - # Finally, re-sort the metadata - return [specifier_metadata_map[c.specifier] for c in challenges] - - -def get_empty_metadata(event_bus: ChallengeEventBus, challenges: List[Challenge]): - return [event_bus.get_manager(c.id).get_default_metadata() for c in challenges] - - -# gets challenges, returning: -# - any existing user_challenge, rolling up aggregate ones -# - for active, non-hidden challenges, returns default state -# - ignores inactive + completed, unless show_historical is true -def get_challenges( - user_id: int, - show_historical: bool, - session: Session, - event_bus: ChallengeEventBus, -) -> List[ChallengeResponse]: - challenges_and_disbursements: List[Tuple[UserChallenge, ChallengeDisbursement]] = ( - session.query(UserChallenge, ChallengeDisbursement) - # Need to do outerjoin because some challenges - # may not have disbursements - .outerjoin( - ChallengeDisbursement, - and_( - ChallengeDisbursement.specifier == UserChallenge.specifier, - ChallengeDisbursement.challenge_id == UserChallenge.challenge_id, - ), - ).filter(UserChallenge.user_id == user_id) - ).all() - - # Filter to challenges that have active managers - # (in practice, all challenge should) - challenges_and_disbursements = [ - c - for c in challenges_and_disbursements - if event_bus.does_manager_exist(c[0].challenge_id) - ] - - # Combine aggregates - - all_challenges: List[Challenge] = (session.query(Challenge)).all() - all_challenges_map = {challenge.id: challenge for challenge in all_challenges} - - # grab user challenges - # if not historical, filter only to *active* challenges - existing_user_challenges: List[UserChallenge] = [ - i[0] - for i in challenges_and_disbursements - if show_historical or all_challenges_map[i[0].challenge_id].active - ] - disbursements: List[ChallengeDisbursement] = [ - i[1] for i in challenges_and_disbursements if i[1] is not None - ] - regular_user_challenges: List[ChallengeResponse] = [] - aggregate_user_challenges_map: DefaultDict[str, List[UserChallenge]] = defaultdict( - lambda: [] - ) - # Get extra metadata - existing_metadata = get_challenges_metadata( - session, event_bus, existing_user_challenges - ) - for i, user_challenge in enumerate(existing_user_challenges): - parent_challenge = all_challenges_map[user_challenge.challenge_id] - if parent_challenge.type == ChallengeType.aggregate: - aggregate_user_challenges_map[user_challenge.challenge_id].append( - user_challenge - ) - else: - # If we're a trending challenge, don't add if the user_challenge is incomplete - if ( - parent_challenge.type == ChallengeType.trending - and not user_challenge.is_complete - ): - continue - user_challenge_dict = to_challenge_response( - user_challenge, - parent_challenge, - get_disbursements_by_challenge_id( - disbursements, user_challenge.challenge_id - ), - existing_metadata[i], - ) - override_step_count = event_bus.get_manager( - parent_challenge.id - ).get_override_challenge_step_count(session, user_id) - if override_step_count is not None and not user_challenge.is_complete: - user_challenge_dict["current_step_count"] = override_step_count - regular_user_challenges.append(user_challenge_dict) - - rolled_up: List[ChallengeResponse] = [] - for challenge_id, challenges in aggregate_user_challenges_map.items(): - parent_challenge = all_challenges_map[challenge_id] - rolled_up.append( - rollup_aggregates( - event_bus, - session, - challenges, - parent_challenge, - get_disbursements_by_challenge_id(disbursements, challenge_id), - ) - ) - - # Return empty user challenges for active challenges that are non-hidden - # and visible for the current user - active_non_hidden_challenges: List[Challenge] = [ - challenge - for challenge in all_challenges - if ( - challenge.active - and not challenge.type == ChallengeType.trending - and event_bus.get_manager(challenge.id).should_show_challenge_for_user( - session, user_id - ) - ) - ] - existing_challenge_ids = { - user_challenge.challenge_id for user_challenge in existing_user_challenges - } - needs_user_challenge = [ - challenge - for challenge in active_non_hidden_challenges - if challenge.id not in existing_challenge_ids - ] - empty_metadata = get_empty_metadata(event_bus, needs_user_challenge) - empty_challenges = create_empty_user_challenges( - user_id, needs_user_challenge, empty_metadata - ) - - combined = regular_user_challenges + rolled_up + empty_challenges - return combined diff --git a/packages/discovery-provider/src/queries/get_collection_library.py b/packages/discovery-provider/src/queries/get_collection_library.py deleted file mode 100644 index 437d2605c15..00000000000 --- a/packages/discovery-provider/src/queries/get_collection_library.py +++ /dev/null @@ -1,269 +0,0 @@ -import enum -from typing import Optional, TypedDict - -from sqlalchemy import asc, desc, or_ -from sqlalchemy.orm import contains_eager -from sqlalchemy.sql.functions import max - -from src.models.playlists.aggregate_playlist import AggregatePlaylist -from src.models.playlists.playlist import Playlist -from src.models.social.repost import Repost, RepostType -from src.models.social.save import Save, SaveType -from src.models.users.usdc_purchase import PurchaseType, USDCPurchase -from src.models.users.user import User -from src.queries import response_name_constants -from src.queries.get_playlists import add_users_to_playlists -from src.queries.query_helpers import ( - CollectionLibrarySortMethod, - LibraryFilterType, - SortDirection, - add_query_pagination, - filter_playlists_with_only_hidden_tracks, - populate_playlist_metadata, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -class CollectionType(str, enum.Enum): - playlist = "playlist" - album = "album" - - -class GetCollectionLibraryArgs(TypedDict): - # The current user logged in (from route param) - user_id: int - - collection_type: CollectionType - - # The maximum number of listens to return - limit: int - - # The offset for the listen history - offset: int - - # One of: all, favorite, repost, purchase - filter_type: LibraryFilterType - - # Include deleted collections - filter_deleted: Optional[bool] - - # Optional filter for the returned results - query: Optional[str] - - sort_direction: Optional[SortDirection] - sort_method: Optional[CollectionLibrarySortMethod] - - -# Most of the code in this file is copied from/similar to src/queries/get_track_library.py - -# see that file for more in depth comments about query structure etc -def _get_collection_library(args: GetCollectionLibraryArgs, session): - user_id = args["user_id"] - limit = args["limit"] - offset = args["offset"] - collection_type = args["collection_type"] - filter_type = args["filter_type"] - - filter_deleted = args.get("filter_deleted", False) - query = args.get("query") - sort_method = args.get("sort_method", CollectionLibrarySortMethod.added_date) - sort_direction = args.get("sort_direction", SortDirection.desc) - - if not filter_type: - raise ValueError("Invalid filter type") - - sort_fn = desc if sort_direction == SortDirection.desc else asc - - own_playlists_base = session.query( - Playlist.playlist_id.label("item_id"), - Playlist.created_at.label("item_created_at"), - ).filter( - Playlist.is_current == True, - Playlist.is_album == (collection_type == CollectionType.album), - Playlist.playlist_owner_id == user_id, - ) - if filter_deleted: - own_playlists_base = own_playlists_base.filter(Playlist.is_delete == False) - - favorites_base = session.query( - Save.save_item_id.label("item_id"), Save.created_at.label("item_created_at") - ).filter( - Save.user_id == user_id, - Save.is_current == True, - Save.is_delete == False, - # Both albums and playlists have SaveType.playlist at the moment, will filter albums with the join as necessary - or_(Save.save_type == SaveType.playlist, Save.save_type == SaveType.album), - ) - - reposts_base = session.query( - Repost.repost_item_id.label("item_id"), - Repost.created_at.label("item_created_at"), - ).filter( - Repost.user_id == user_id, - Repost.is_current == True, - Repost.is_delete == False, - # Both albums and playlists have RepostType.playlist at the moment, will filter albums with the join as necessary - or_( - Repost.repost_type == RepostType.playlist, - Repost.repost_type == RepostType.album, - ), - ) - - purchase_base = session.query( - USDCPurchase.content_id.label("item_id"), - USDCPurchase.created_at.label("item_created_at"), - ).filter( - USDCPurchase.content_type == PurchaseType.album, - USDCPurchase.buyer_user_id == user_id, - ) - - # Union everything for the "all" query - union_subquery = favorites_base.union_all( - reposts_base, own_playlists_base, purchase_base - ).subquery(name="union_subquery") - # Remove dupes - all_base = ( - session.query( - union_subquery.c.item_id, - max(union_subquery.c.item_created_at).label("item_created_at"), - ) - .select_from(union_subquery) - .group_by(union_subquery.c.item_id) - ) - - # Favorites are unioned with own playlists by design - favorites_base = favorites_base.union_all(own_playlists_base) - - subquery = { - LibraryFilterType.all: all_base.subquery("library"), - LibraryFilterType.favorite: favorites_base.subquery(name="favorites_and_own"), - LibraryFilterType.repost: reposts_base.subquery(name="reposts"), - LibraryFilterType.purchase: purchase_base.subquery(name="purchases"), - }[filter_type] - - base_query = ( - session.query(Playlist, subquery.c.item_created_at) - .select_from(subquery) - .join(Playlist, Playlist.playlist_id == subquery.c.item_id) - .filter( - Playlist.is_current == True, - Playlist.is_album == (collection_type == CollectionType.album), - ) - ) - - if filter_deleted: - base_query = base_query.filter(Playlist.is_delete == False) - - if query: - # Need to disable lazy join for this to work - base_query = ( - base_query.join(Playlist.user.of_type(User)) - .options(contains_eager(Playlist.user)) - .filter( - or_( - Playlist.playlist_name.ilike(f"%{query.lower()}%"), - User.name.ilike(f"%{query.lower()}%"), - ) - ) - ) - - # Set sort methods - if sort_method == CollectionLibrarySortMethod.added_date: - base_query = base_query.order_by( - sort_fn(subquery.c.item_created_at), desc(Playlist.playlist_id) - ) - elif sort_method == CollectionLibrarySortMethod.reposts: - base_query = base_query.join( - AggregatePlaylist, - AggregatePlaylist.playlist_id == Playlist.playlist_id, - ).order_by(sort_fn(AggregatePlaylist.repost_count)) - - elif sort_method == CollectionLibrarySortMethod.saves: - base_query = base_query.join( - AggregatePlaylist, - AggregatePlaylist.playlist_id == Playlist.playlist_id, - ).order_by(sort_fn(AggregatePlaylist.save_count)) - - query_results = add_query_pagination(base_query, limit, offset).all() - - if not query_results: - return [] - - playlist_results, activity_timestamp = zip(*query_results) - playlists = helpers.query_result_to_list(playlist_results) - playlist_ids = list(map(lambda playlist: playlist["playlist_id"], playlists)) - - # Populate playlists - playlists = populate_playlist_metadata( - session, - playlist_ids, - playlists, - [RepostType.playlist, RepostType.album], - [SaveType.playlist, SaveType.album], - user_id, - ) - - # exclude hidden playlists and hidden albums which were not previously purchase by current user - album_purchases = set() - album_ids = ( - [p["playlist_id"] for p in playlists] - if collection_type == CollectionType.album - else [] - ) - if album_ids: - album_purchases = ( - session.query(USDCPurchase.content_id) - .filter( - USDCPurchase.buyer_user_id == user_id, - USDCPurchase.content_id.in_(album_ids), - USDCPurchase.content_type == "album", - ) - .all() - ) - album_purchases = set([p[0] for p in album_purchases]) - - # We don't want to filter any of the user's own playlists in the logic below - own_playlist_ids = { - p["playlist_id"] for p in playlists if p["playlist_owner_id"] == user_id - } - - playlists = list( - filter( - lambda playlist: playlist["playlist_id"] in own_playlist_ids - or not playlist["is_private"] - or playlist["playlist_id"] in album_purchases, - playlists, - ) - ) - - playlist_track_ids = set() - for playlist in playlists: - track_ids = set( - map( - lambda t: t["track"], - playlist.get("playlist_contents", {}).get("track_ids", []), - ) - ) - playlist_track_ids = playlist_track_ids.union(track_ids) - - # exclude playlists with only hidden tracks and empty playlists - playlists = filter_playlists_with_only_hidden_tracks( - session, - playlists, - playlist_track_ids, - ignore_ids=list(album_purchases | own_playlist_ids), - ) - - # attach users - playlists = add_users_to_playlists(playlists, session, user_id) - - for idx, playlist in enumerate(playlists): - playlist[response_name_constants.activity_timestamp] = activity_timestamp[idx] - - return playlists - - -def get_collection_library(args: GetCollectionLibraryArgs): - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_collection_library(args, session) diff --git a/packages/discovery-provider/src/queries/get_follow_intersection_users.py b/packages/discovery-provider/src/queries/get_follow_intersection_users.py deleted file mode 100644 index 4bdb5440220..00000000000 --- a/packages/discovery-provider/src/queries/get_follow_intersection_users.py +++ /dev/null @@ -1,41 +0,0 @@ -from sqlalchemy.sql import text - -from src.queries.get_unpopulated_users import get_unpopulated_users -from src.queries.query_helpers import populate_user_metadata -from src.utils.db_session import get_db_read_replica - -sql = text( - """ -select - x.follower_user_id -from follows x -join aggregate_user au on x.follower_user_id = au.user_id -join follows me - on me.follower_user_id = :my_id - and me.followee_user_id = x.follower_user_id - and me.is_delete = false -where x.followee_user_id = :other_user_id - and x.is_delete = false -order by follower_count desc -limit :limit -offset :offset -""" -) - - -def get_follow_intersection_users(args): - users = [] - my_id = args.get("my_id") - - db = get_db_read_replica() - with db.scoped_session() as session: - rows = session.execute(sql, args) - user_ids = [r[0] for r in rows] - - # get all users for above user_ids - users = get_unpopulated_users(session, user_ids) - - # bundle peripheral info into user results - users = populate_user_metadata(session, user_ids, users, my_id) - - return users diff --git a/packages/discovery-provider/src/queries/get_genre_metrics.py b/packages/discovery-provider/src/queries/get_genre_metrics.py deleted file mode 100644 index 180664baef8..00000000000 --- a/packages/discovery-provider/src/queries/get_genre_metrics.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging - -from sqlalchemy import desc, func - -from src.models.tracks.track import Track -from src.utils import db_session -from src.utils.hardcoded_data import genre_allowlist - -logger = logging.getLogger(__name__) - - -def get_genre_metrics(args): - """ - Returns metrics for track genres over the provided bucket - - Args: - args: dict The parsed args from the request - args.offset: number The offset to start querying from - args.limit: number The max number of queries to return - args.start_time: date The start of the query - - Returns: - Array of dictionaries with the play counts and timestamp - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_genre_metrics(session, args) - - -def _get_genre_metrics(session, args): - metrics_query = ( - session.query(Track.genre, func.count(Track.track_id).label("count")) - .filter( - Track.genre != None, - Track.genre != "", - Track.genre.in_(genre_allowlist), - Track.is_current == True, - Track.created_at > args.get("start_time"), - ) - .group_by(Track.genre) - .order_by(desc("count")) - .limit(args.get("limit")) - .offset(args.get("offset")) - ) - - metrics = metrics_query.all() - genres = [{"name": m[0], "count": m[1]} for m in metrics] - - return genres diff --git a/packages/discovery-provider/src/queries/get_genre_metrics_unit_test.py b/packages/discovery-provider/src/queries/get_genre_metrics_unit_test.py deleted file mode 100644 index 4948559bc05..00000000000 --- a/packages/discovery-provider/src/queries/get_genre_metrics_unit_test.py +++ /dev/null @@ -1,2 +0,0 @@ -def test(): - """See /integration_tests/test_get_genre_metrics.py""" diff --git a/packages/discovery-provider/src/queries/get_historical_route_metrics.py b/packages/discovery-provider/src/queries/get_historical_route_metrics.py deleted file mode 100644 index b4aca68ee1e..00000000000 --- a/packages/discovery-provider/src/queries/get_historical_route_metrics.py +++ /dev/null @@ -1,133 +0,0 @@ -import functools as ft -import logging -from datetime import date, timedelta - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -def get_historical_route_metrics(): - """ - Returns daily metrics for the last thirty days and all time monthly metrics - - Returns: - { - daily: { - 2021/01/15: {unique_count: ..., total_count: ...} - ... - }, - monthly: { - 2021/01/01: {unique_count: ..., total_count: ...} - ... - } - } - """ - - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_historical_route_metrics(session) - - -def _get_historical_route_metrics(session): - today = date.today() - thirty_days_ago = today - timedelta(days=30) - first_day_of_month = today.replace(day=1) - - daily_metrics = {} - unique_daily_counts = ( - session.query( - AggregateDailyUniqueUsersMetrics.timestamp, - AggregateDailyUniqueUsersMetrics.count, - AggregateDailyUniqueUsersMetrics.summed_count, - ) - .filter(thirty_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .all() - ) - unique_daily_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_daily_counts, - {}, - ) - - total_daily_counts = ( - session.query( - AggregateDailyTotalUsersMetrics.timestamp, - AggregateDailyTotalUsersMetrics.count, - ) - .filter(thirty_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .all() - ) - total_daily_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_daily_counts, - {}, - ) - - for timestamp, counts in unique_daily_count_records.items(): - if timestamp in total_daily_count_records: - daily_metrics[timestamp] = { - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_daily_count_records[timestamp], - } - - monthly_metrics = {} - unique_monthly_counts = ( - session.query( - AggregateMonthlyUniqueUsersMetric.timestamp, - AggregateMonthlyUniqueUsersMetric.count, - AggregateMonthlyUniqueUsersMetric.summed_count, - ) - .filter(AggregateMonthlyUniqueUsersMetric.timestamp < first_day_of_month) - .all() - ) - unique_monthly_count_records = ft.reduce( - lambda acc, curr: acc.update( - {str(curr[0]): {"unique": curr[1], "summed_unique": curr[2] or 0}} - ) - or acc, - unique_monthly_counts, - {}, - ) - - total_monthly_counts = ( - session.query( - AggregateMonthlyTotalUsersMetric.timestamp, - AggregateMonthlyTotalUsersMetric.count, - ) - .filter(AggregateMonthlyTotalUsersMetric.timestamp < first_day_of_month) - .all() - ) - total_monthly_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_monthly_counts, - {}, - ) - - for timestamp, counts in unique_monthly_count_records.items(): - if timestamp in total_monthly_count_records: - monthly_metrics[timestamp] = { - "unique_count": counts["unique"], - "summed_unique_count": counts["summed_unique"], - "total_count": total_monthly_count_records[timestamp], - } - - return {"daily": daily_metrics, "monthly": monthly_metrics} diff --git a/packages/discovery-provider/src/queries/get_notifications.py b/packages/discovery-provider/src/queries/get_notifications.py deleted file mode 100644 index 11d0360f453..00000000000 --- a/packages/discovery-provider/src/queries/get_notifications.py +++ /dev/null @@ -1,719 +0,0 @@ -import logging -from collections import defaultdict -from datetime import datetime -from enum import Enum -from typing import Dict, List, Optional, TypedDict, Union - -from sqlalchemy import bindparam, text -from sqlalchemy.orm.session import Session - -from src.models.tracks.track import Track - -logger = logging.getLogger(__name__) - -notification_groups_sql = text( - """ ---- Create Intervals of user seen -WITH user_seen as ( - SELECT - LAG(seen_at, 1, now()::timestamp) OVER ( ORDER BY seen_at desc ) AS seen_at, - seen_at as prev_seen_at - FROM - notification_seen - WHERE - user_id = :user_id - ORDER BY - seen_at desc -), user_created_at as ( - SELECT - created_at - FROM - users - WHERE - user_id = :user_id - AND is_current -) -SELECT - n.type, - n.group_id as group_id, - array_agg(n.id), - CASE - WHEN user_seen.seen_at is not NULL THEN now()::timestamp != user_seen.seen_at - ELSE EXISTS(SELECT 1 from notification_seen where user_id = :user_id) - END as is_seen, - CASE - WHEN user_seen.seen_at is not NULL THEN user_seen.seen_at - ELSE ( - SELECT seen_at from notification_seen - WHERE user_id = :user_id - ORDER BY seen_at ASC - limit 1 - ) - END as seen_at, - user_seen.prev_seen_at, - count(n.group_id), - max(n.timestamp) as latest_timestamp -FROM - notification n -LEFT JOIN user_seen on - user_seen.seen_at >= n.timestamp and user_seen.prev_seen_at < n.timestamp -WHERE - ((ARRAY[:user_id] && n.user_ids) OR (n.type = 'announcement' AND n.timestamp > (SELECT created_at FROM user_created_at))) AND - (:valid_types is NOT NULL AND n.type in :valid_types) AND - ( - (:timestamp_offset is NULL AND :group_id_offset is NULL) OR - (:timestamp_offset is NULL AND :group_id_offset is NOT NULL AND n.group_id < :group_id_offset) OR - (:timestamp_offset is NOT NULL AND n.timestamp < :timestamp_offset) OR - ( - :group_id_offset is NOT NULL AND :timestamp_offset is NOT NULL AND - (n.timestamp = :timestamp_offset AND n.group_id < :group_id_offset) - ) - ) -GROUP BY - n.type, n.group_id, user_seen.seen_at, user_seen.prev_seen_at -ORDER BY - user_seen.seen_at desc NULLS LAST, - max(n.timestamp) desc, - n.group_id desc -limit :limit; -""" -) -notification_groups_sql = notification_groups_sql.bindparams( - bindparam("valid_types", expanding=True) -) - -unread_notification_count_sql = text( - """ ---- Create Intervals of user seen -WITH user_created_at as ( - SELECT - created_at - FROM - users - WHERE - user_id = :user_id - AND is_current -) -SELECT - count(*) -FROM ( - select n.type, n.group_id - from - notification n - WHERE - ((ARRAY[:user_id] && n.user_ids) OR (n.type = 'announcement' AND n.timestamp > (SELECT created_at FROM user_created_at))) AND - (:valid_types is NOT NULL AND n.type in :valid_types) AND - n.timestamp > COALESCE(( - SELECT - seen_at - FROM - notification_seen - WHERE - user_id = :user_id - ORDER BY seen_at desc - LIMIT 1 - ), '2016-01-01'::timestamp) - GROUP BY - n.type, n.group_id -) user_notifications; -""" -) -unread_notification_count_sql = unread_notification_count_sql.bindparams( - bindparam("valid_types", expanding=True) -) - - -MAX_LIMIT = 50 -DEFAULT_LIMIT = 20 - - -class NotificationGroup(TypedDict): - type: str - group_id: str - notification_ids: List[int] - is_seen: bool - seen_at: datetime - prev_seen_at: datetime - count: int - - -class NotificationType(str, Enum): - ANNOUNCEMENT = "announcement" - FOLLOW = "follow" - REPOST = "repost" - SAVE = "save" - REMIX = "remix" - COSIGN = "cosign" - CREATE = "create" - TIP_RECEIVE = "tip_receive" - TIP_SEND = "tip_send" - CHALLENGE_REWARD = "challenge_reward" - REPOST_OF_REPOST = "repost_of_repost" - SAVE_OF_REPOST = "save_of_repost" - TASTEMAKER = "tastemaker" - REACTION = "reaction" - SUPPORTER_DETRONED = "supporter_dethroned" - SUPPORTER_RANK_UP = "supporter_rank_up" - SUPPORTING_RANK_UP = "supporting_rank_up" - MILESTONE = "milestone" - TRACK_MILESTONE = "track_milestone" - TRACK_ADDED_TO_PLAYLIST = "track_added_to_playlist" - PLAYLIST_MILSTONE = "playlist_milestone" - TIER_CHANGE = "tier_change" - TRENDING = "trending" - TRENDING_PLAYLIST = "trending_playlist" - TRENDING_UNDERGROUND = "trending_underground" - USDC_PURCHASE_BUYER = "usdc_purchase_buyer" - USDC_PURCHASE_SELLER = "usdc_purchase_seller" - TRACK_ADDED_TO_PURCHASED_ALBUM = "track_added_to_purchased_album" - REQUEST_MANAGER = "request_manager" - APPROVE_MANAGER_REQUEST = "approve_manager_request" - CLAIMABLE_REWARD = "claimable_reward" - COMMENT = "comment" - COMMENT_THREAD = "comment_thread" - COMMENT_MENTION = "comment_mention" - COMMENT_REACTION = "comment_reaction" - LISTEN_STREAK_REMINDER = "listen_streak_reminder" - FAN_REMIX_CONTEST_STARTED = "fan_remix_contest_started" - FAN_REMIX_CONTEST_ENDED = "fan_remix_contest_ended" - FAN_REMIX_CONTEST_ENDING_SOON = "fan_remix_contest_ending_soon" - FAN_REMIX_CONTEST_WINNERS_SELECTED = "fan_remix_contest_winners_selected" - ARTIST_REMIX_CONTEST_ENDED = "artist_remix_contest_ended" - ARTIST_REMIX_CONTEST_ENDING_SOON = "artist_remix_contest_ending_soon" - ARTIST_REMIX_CONTEST_SUBMISSIONS = "artist_remix_contest_submissions" - # Indexer/pedalboard emit these types but they were missing from the - # enum, so the parser's `oneof` validation rejected them as - # `valid_types` args and the default list below couldn't include - # them. Add the missing members so the frontend can opt in/out and - # the default-all path returns them. - FAN_REMIX_CONTEST_SUBMISSION = "fan_remix_contest_submission" - FAN_CLUB_TEXT_POST = "fan_club_text_post" - REMIX_CONTEST_UPDATE = "remix_contest_update" - - def __str__(self) -> str: - return str.__str__(self) - - -# Default-on notification types when the client doesn't enumerate them -# explicitly (or sends an empty list). The set mirrors the Go API's -# `oneof=...` whitelist on /v1/notifications (api.audius.co repo, -# api/v1_notifications.go) which is the canonical source of truth for -# "types the frontend knows how to render". Adding a new user-facing -# notification type means appending it here AND to the Go API -# whitelist; deprecated/broken types are owned by the Go API's -# `unsupportedNotificationTypes` deny-list and intentionally omitted -# here too. -default_valid_notification_types = [ - NotificationType.ANNOUNCEMENT, - NotificationType.FOLLOW, - NotificationType.REPOST, - NotificationType.SAVE, - NotificationType.COSIGN, - NotificationType.CREATE, - NotificationType.TIP_RECEIVE, - NotificationType.TIP_SEND, - NotificationType.CHALLENGE_REWARD, - NotificationType.REPOST_OF_REPOST, - NotificationType.SAVE_OF_REPOST, - NotificationType.TASTEMAKER, - NotificationType.REACTION, - NotificationType.SUPPORTER_DETRONED, - NotificationType.SUPPORTER_RANK_UP, - NotificationType.SUPPORTING_RANK_UP, - NotificationType.MILESTONE, - NotificationType.TRACK_ADDED_TO_PLAYLIST, - NotificationType.TIER_CHANGE, - NotificationType.TRENDING, - NotificationType.TRENDING_PLAYLIST, - NotificationType.TRENDING_UNDERGROUND, - NotificationType.USDC_PURCHASE_BUYER, - NotificationType.USDC_PURCHASE_SELLER, - NotificationType.TRACK_ADDED_TO_PURCHASED_ALBUM, - NotificationType.REQUEST_MANAGER, - NotificationType.APPROVE_MANAGER_REQUEST, - NotificationType.CLAIMABLE_REWARD, - NotificationType.COMMENT, - NotificationType.COMMENT_THREAD, - NotificationType.COMMENT_MENTION, - NotificationType.COMMENT_REACTION, - NotificationType.LISTEN_STREAK_REMINDER, - NotificationType.FAN_REMIX_CONTEST_STARTED, - NotificationType.FAN_REMIX_CONTEST_ENDED, - NotificationType.FAN_REMIX_CONTEST_ENDING_SOON, - NotificationType.FAN_REMIX_CONTEST_WINNERS_SELECTED, - NotificationType.FAN_REMIX_CONTEST_SUBMISSION, - NotificationType.ARTIST_REMIX_CONTEST_ENDED, - NotificationType.ARTIST_REMIX_CONTEST_ENDING_SOON, - NotificationType.ARTIST_REMIX_CONTEST_SUBMISSIONS, - NotificationType.FAN_CLUB_TEXT_POST, - NotificationType.REMIX_CONTEST_UPDATE, -] - - -class GetNotificationArgs(TypedDict): - user_id: int - timestamp: Optional[datetime] - group_id: Optional[str] - limit: Optional[int] - valid_types: Optional[List[NotificationType]] - - -def get_notification_groups(session: Session, args: GetNotificationArgs): - """ - Gets the user's notifications in the database - """ - valid_types = args.get("valid_types", default_valid_notification_types) - limit = args.get("limit") or DEFAULT_LIMIT - limit = min(limit, MAX_LIMIT) # type: ignore - - rows = session.execute( - notification_groups_sql, - { - "user_id": args["user_id"], - "limit": limit, - "timestamp_offset": args.get("timestamp", None), - "group_id_offset": args.get("group_id", None), - "valid_types": valid_types, - }, - ) - - res: List[NotificationGroup] = [ - { - "type": r[0], - "group_id": r[1], - "notification_ids": r[2], - "is_seen": r[3] if r[3] != None else False, - "seen_at": r[4], - "prev_seen_at": r[5], - "count": r[6], - } - for r in rows - ] - - return res - - -class FollowNotification(TypedDict): - follower_user_id: int - followee_user_id: int - - -class RepostNotification(TypedDict): - type: str - user_id: int - repost_item_id: int - - -class RepostOfRepostNotification(TypedDict): - type: str - user_id: int - repost_of_repost_item_id: int - - -class SaveOfRepostNotification(TypedDict): - type: str - user_id: int - save_of_repost_item_id: int - - -class TastemakerNotification(TypedDict): - tastemaker_item_id: int - tastemaker_item_owner_id: int - action: str - tastemaker_item_type: str - tastemaker_user_id: int - - -class SaveNotification(TypedDict): - type: str - user_id: int - save_item_id: int - - -class RemixNotification(TypedDict): - parent_track_id: int - track_id: int - - -class CosignRemixNotification(TypedDict): - parent_track_id: int - track_owner_id: int - track_id: int - - -class CreateTrackNotification(TypedDict): - track_id: int - - -class CreatePlaylistNotification(TypedDict): - is_album: bool - playlist_id: int - - -class TipReceiveNotification(TypedDict): - amount: int - sender_user_id: int - receiver_user_id: int - tx_signature: str - reaction_value: Optional[int] - - -class TipSendNotification(TypedDict): - amount: int - sender_user_id: int - receiver_user_id: int - tx_signature: str - - -class ChallengeRewardNotification(TypedDict): - amount: int - specifier: str - challenge_id: str - listen_streak: Optional[int] - - -class ClaimableRewardNotification(TypedDict): - amount: int - specifier: str - challenge_id: str - - -class ReactionNotification(TypedDict): - reacted_to: str - reaction_type: str - reaction_value: int - sender_wallet: str - receiver_user_id: int - sender_user_id: int - tip_amount: int - - -class SupporterRankUpNotification(TypedDict): - rank: int - sender_user_id: int - receiver_user_id: int - - -class SupportingRankUpNotification(TypedDict): - rank: int - sender_user_id: int - receiver_user_id: int - - -class SupporterDethronedNotification(TypedDict): - sender_user_id: int - receiver_user_id: int - dethroned_user_id: int - - -class FollowerMilestoneNotification(TypedDict): - type: str - user_id: int - threshold: int - - -class TrackMilestoneNotification(TypedDict): - type: str - track_id: int - threshold: int - - -class PlaylistMilestoneNotification(TypedDict): - type: str - playlist_id: int - threshold: int - is_album: bool - - -class TierChangeNotification(TypedDict): - new_tier: str - new_tier_value: int - current_value: str - - -class TrackAddedToPlaylistNotification(TypedDict): - track_id: int - playlist_id: int - playlist_owner_id: int - - -class TrackAddedToPurchasedAlbumNotification(TypedDict): - track_id: int - playlist_id: int - playlist_owner_id: int - - -class TrendingNotification(TypedDict): - rank: int - genre: str - track_id: int - time_range: str - - -class TrendingPlaylistNotification(TypedDict): - rank: int - genre: str - playlist_id: int - time_range: str - - -class TrendingUndergroundNotification(TypedDict): - rank: int - genre: str - track_id: int - time_range: str - - -class UsdcPurchaseSellerNotification(TypedDict): - content_type: str - content_id: int - buyer_user_id: int - seller_user_id: int - amount: int - extra_amount: int - - -class UsdcPurchaseBuyerNotification(TypedDict): - content_type: str - content_id: int - buyer_user_id: int - seller_user_id: int - amount: int - extra_amount: int - - -class RequestManagerNotification(TypedDict): - user_id: int - grantee_user_id: int - grantee_address: str - - -class ApproveManagerNotification(TypedDict): - user_id: int - grantee_user_id: int - grantee_address: str - - -class AnnouncementNotification(TypedDict): - title: str - short_description: str - long_description: Optional[str] - - -class CommentNotification(TypedDict): - type: str - entity_id: int - comment_user_id: int - comment_id: Optional[int] - - -class CommentThreadNotification(TypedDict): - type: str - entity_id: int - entity_user_id: int - comment_user_id: int - comment_id: Optional[int] - - -class CommentMentionNotification(TypedDict): - type: str - entity_id: int - entity_user_id: int - comment_user_id: int - comment_id: Optional[int] - - -class CommentReactionNotification(TypedDict): - type: str - entity_id: int - entity_user_id: int - reacter_user_id: int - comment_id: Optional[int] - - -class ListenStreakReminderNotification(TypedDict): - streak: int - - -class FanRemixContestStartedNotification(TypedDict): - entity_user_id: int - entity_id: int - - -class FanRemixContestEndedNotification(TypedDict): - entity_user_id: int - entity_id: int - - -class FanRemixContestEndingSoonNotification(TypedDict): - entity_user_id: int - entity_id: int - - -class FanRemixContestWinnersSelectedNotification(TypedDict): - entity_user_id: int - entity_id: int - - -class ArtistRemixContestEndedNotification(TypedDict): - entity_id: int - - -class ArtistRemixContestEndingSoonNotification(TypedDict): - entity_user_id: int - entity_id: int - - -class ArtistRemixContestSubmissionsNotification(TypedDict): - event_id: int - milestone: int - entity_id: int - - -NotificationData = Union[ - AnnouncementNotification, - FollowNotification, - RepostNotification, - RepostOfRepostNotification, - SaveOfRepostNotification, - SaveNotification, - RemixNotification, - CosignRemixNotification, - CreateTrackNotification, - CreatePlaylistNotification, - TastemakerNotification, - TipReceiveNotification, - TipSendNotification, - TrackAddedToPlaylistNotification, - ChallengeRewardNotification, - ReactionNotification, - SupporterRankUpNotification, - SupportingRankUpNotification, - SupporterDethronedNotification, - FollowerMilestoneNotification, - TrackMilestoneNotification, - PlaylistMilestoneNotification, - TierChangeNotification, - TrendingNotification, - UsdcPurchaseBuyerNotification, - UsdcPurchaseSellerNotification, - CommentNotification, - CommentThreadNotification, - CommentMentionNotification, - CommentReactionNotification, - ListenStreakReminderNotification, - FanRemixContestStartedNotification, - FanRemixContestEndedNotification, - FanRemixContestEndingSoonNotification, - FanRemixContestWinnersSelectedNotification, - ArtistRemixContestEndedNotification, - ArtistRemixContestEndingSoonNotification, - ArtistRemixContestSubmissionsNotification, -] - - -class NotificationAction(TypedDict): - specifier: str - type: str - group_id: str - timestamp: datetime - data: NotificationData - - -class Notification(TypedDict): - type: str - group_id: str - is_seen: bool - seen_at: datetime - actions: List[NotificationAction] - - -notifications_sql = text( - """ -SELECT - id, - type, - specifier, - timestamp, - data, - group_id -FROM notification n -WHERE n.id in :notification_ids -""" -) -notifications_sql = notifications_sql.bindparams( - bindparam("notification_ids", expanding=True) -) - - -def get_notifications(session: Session, args: GetNotificationArgs): - args["valid_types"] = args.get("valid_types", []) + default_valid_notification_types # type: ignore - - notifications = get_notification_groups(session, args) - notification_ids = [] - for notification in notifications: - notification_ids.extend(notification["notification_ids"]) - - rows = session.execute(notifications_sql, {"notification_ids": notification_ids}) - - notification_id_data: Dict[int, NotificationAction] = defaultdict() - for row in rows: - notification_id_data[row[0]] = { - "type": row[1], - "specifier": row[2], - "timestamp": row[3], - "data": row[4], - "group_id": row[5], - } - - notifications_and_actions = [ - { - **notification, - "actions": sorted( - [notification_id_data[id] for id in notification["notification_ids"]], - key=lambda x: x["specifier"], - ), - } - for notification in notifications - ] - - # TODO(PAY-1880): Remove this check after launch - if NotificationType.USDC_PURCHASE_BUYER not in args["valid_types"]: # type: ignore - # Filter out usdc create tracks - filtered: List[NotificationGroup] = [] - for notification in notifications_and_actions: - is_track = "track_id" in notification["actions"][0]["data"] - if notification["type"] == NotificationType.CREATE and is_track: - res = ( - session.query(Track).filter( - Track.track_id == notification["actions"][0]["data"]["track_id"] - ) - ).one_or_none() - if ( - res - and res.stream_conditions - and "usdc_purchase" in res.stream_conditions - ): - # Filter out the notification - continue - filtered.append(notification) - return filtered - - return notifications_and_actions - - -class GetUnreadNotificationCount(TypedDict): - user_id: int - valid_types: Optional[List[NotificationType]] - - -def get_unread_notification_count(session: Session, args: GetUnreadNotificationCount): - args["valid_types"] = args.get("valid_types", []) + default_valid_notification_types # type: ignore - resultproxy = session.execute( - unread_notification_count_sql, - {"user_id": args["user_id"], "valid_types": args.get("valid_types", None)}, - ) - unread_count = 0 - for rowproxy in resultproxy: - unread_count = rowproxy[0] - return unread_count diff --git a/packages/discovery-provider/src/queries/get_personal_route_metrics.py b/packages/discovery-provider/src/queries/get_personal_route_metrics.py deleted file mode 100644 index bb7084d2485..00000000000 --- a/packages/discovery-provider/src/queries/get_personal_route_metrics.py +++ /dev/null @@ -1,304 +0,0 @@ -import functools as ft -import logging -from datetime import date, timedelta - -from sqlalchemy import asc, func - -from src import exceptions -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.models.metrics.aggregate_monthly_total_users_metrics import ( - AggregateMonthlyTotalUsersMetric, -) -from src.models.metrics.aggregate_monthly_unique_users_metrics import ( - AggregateMonthlyUniqueUsersMetric, -) -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -def get_personal_route_metrics(time_range, bucket_size): - """ - Returns a list of timestamps with unique count and total count for all routes - on this node based on given time range and grouped by bucket size - - Returns: - [{ timestamp, unique_count, total_count }] - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_personal_route_metrics(session, time_range, bucket_size) - - -def _get_personal_route_metrics(session, time_range, bucket_size): - today = date.today() - seven_days_ago = today - timedelta(days=7) - thirty_days_ago = today - timedelta(days=30) - first_day_of_month = today.replace(day=1) - - if time_range == "week": - if bucket_size == "day": - unique_counts = ( - session.query( - AggregateDailyUniqueUsersMetrics.timestamp, - AggregateDailyUniqueUsersMetrics.personal_count, - ) - .filter(seven_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateDailyTotalUsersMetrics.timestamp, - AggregateDailyTotalUsersMetrics.personal_count, - ) - .filter(seven_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, unique_count in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": unique_count, - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - if time_range == "month": - if bucket_size == "day": - unique_counts = ( - session.query( - AggregateDailyUniqueUsersMetrics.timestamp, - AggregateDailyUniqueUsersMetrics.personal_count, - ) - .filter(thirty_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateDailyTotalUsersMetrics.timestamp, - AggregateDailyTotalUsersMetrics.personal_count, - ) - .filter(thirty_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, unique_count in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": unique_count, - "total_count": total_count_records[timestamp], - } - ) - return metrics - if bucket_size == "week": - unique_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyUniqueUsersMetrics.personal_count).label( - "count" - ), - ) - .filter(thirty_days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyTotalUsersMetrics.personal_count).label( - "count" - ), - ) - .filter(thirty_days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, unique_count in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": unique_count, - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - if time_range == "all_time": - if bucket_size == "month": - unique_counts = ( - session.query( - AggregateMonthlyUniqueUsersMetric.timestamp, - AggregateMonthlyUniqueUsersMetric.personal_count, - ) - .filter( - AggregateMonthlyUniqueUsersMetric.timestamp < first_day_of_month - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - AggregateMonthlyTotalUsersMetric.timestamp, - AggregateMonthlyTotalUsersMetric.personal_count, - ) - .filter(AggregateMonthlyTotalUsersMetric.timestamp < first_day_of_month) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, unique_count in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": unique_count, - "total_count": total_count_records[timestamp], - } - ) - return metrics - if bucket_size == "week": - unique_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyUniqueUsersMetrics.personal_count).label( - "count" - ), - ) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyUniqueUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - unique_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - unique_counts, - {}, - ) - - total_counts = ( - session.query( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ).label("timestamp"), - func.sum(AggregateDailyTotalUsersMetrics.personal_count).label( - "count" - ), - ) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .group_by( - func.date_trunc( - bucket_size, AggregateDailyTotalUsersMetrics.timestamp - ) - ) - .order_by(asc("timestamp")) - .all() - ) - total_count_records = ft.reduce( - lambda acc, curr: acc.update({str(curr[0]): curr[1]}) or acc, - total_counts, - {}, - ) - - metrics = [] - for timestamp, unique_count in unique_count_records.items(): - if timestamp in total_count_records: - metrics.append( - { - "timestamp": timestamp, - "unique_count": unique_count, - "total_count": total_count_records[timestamp], - } - ) - return metrics - raise exceptions.ArgumentError("Invalid bucket_size for time_range") - raise exceptions.ArgumentError("Invalid time_range") diff --git a/packages/discovery-provider/src/queries/get_plays_metrics.py b/packages/discovery-provider/src/queries/get_plays_metrics.py deleted file mode 100644 index 10cf81a4e80..00000000000 --- a/packages/discovery-provider/src/queries/get_plays_metrics.py +++ /dev/null @@ -1,62 +0,0 @@ -import logging -import time -from typing import TypedDict - -from sqlalchemy import desc, func -from sqlalchemy.orm.session import Session - -from src.models.social.hourly_play_counts import HourlyPlayCount -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -class GetPlayMetricsArgs(TypedDict): - # A date_trunc operation to aggregate timestamps by - bucket_size: int - - # The max number of responses to return - start_time: int - - # The max number of responses to return - limit: int - - -def get_plays_metrics(args: GetPlayMetricsArgs): - """ - Returns metrics for play counts - - Args: - args: GetPlayMetrics the parsed args from the request - - Returns: - Array of dictionaries with the play counts and timestamp - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_plays_metrics(session, args) - - -def _get_plays_metrics(session: Session, args: GetPlayMetricsArgs): - metrics_query = ( - session.query( - func.date_trunc( - args.get("bucket_size"), HourlyPlayCount.hourly_timestamp - ).label("timestamp"), - func.sum(HourlyPlayCount.play_count).label("count"), - ) - .filter(HourlyPlayCount.hourly_timestamp > args.get("start_time")) - .group_by( - func.date_trunc(args.get("bucket_size"), HourlyPlayCount.hourly_timestamp) - ) - .order_by(desc("timestamp")) - .limit(args.get("limit")) - ) - - metrics = metrics_query.all() - - metrics = [ - {"timestamp": int(time.mktime(metric[0].timetuple())), "count": metric[1]} - for metric in metrics - ] - return metrics diff --git a/packages/discovery-provider/src/queries/get_plays_metrics_unit_test.py b/packages/discovery-provider/src/queries/get_plays_metrics_unit_test.py deleted file mode 100644 index c74948a8499..00000000000 --- a/packages/discovery-provider/src/queries/get_plays_metrics_unit_test.py +++ /dev/null @@ -1,2 +0,0 @@ -def test(): - """See /integration_tests/test_get_plays_metrics.py""" diff --git a/packages/discovery-provider/src/queries/get_prev_track_entries.py b/packages/discovery-provider/src/queries/get_prev_track_entries.py deleted file mode 100644 index 0fd5ca1b358..00000000000 --- a/packages/discovery-provider/src/queries/get_prev_track_entries.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import List - -from sqlalchemy import and_, func, or_ -from sqlalchemy.orm.session import Session - -from src.models.tracks.track import Track - - -def get_prev_track_entries(session: Session, entries: List[Track]): - """ - Gets the previous state of tracks in the database given a list of tracks. - - Args: - session: (DB) sqlalchemy scoped db session - entries: (List) List of current track entries - - Returns: - prev_track_entries: (List) List of previous track entries corresponding to the passed track entries - """ - - if len(entries) == 0: - return [] - - def get_prev_query_pairs(entry): - return [entry.track_id, entry.blocknumber] - - prev_query_pairs = map(get_prev_query_pairs, entries) - - prev_entries_subquery = ( - session.query( - Track.track_id, func.max(Track.blocknumber).label("max_blocknumber") - ) - .filter( - or_( - *( - and_(Track.track_id == pair[0], Track.blocknumber < pair[1]) - for pair in prev_query_pairs - ) - ) - ) - .group_by(Track.track_id) - .subquery() - ) - - prev_entries_query = session.query(Track).join( - prev_entries_subquery, - and_( - prev_entries_subquery.c.track_id == Track.track_id, - prev_entries_subquery.c.max_blocknumber == Track.blocknumber, - ), - ) - - return prev_entries_query.all() diff --git a/packages/discovery-provider/src/queries/get_related_artists.py b/packages/discovery-provider/src/queries/get_related_artists.py deleted file mode 100644 index 404edca65d3..00000000000 --- a/packages/discovery-provider/src/queries/get_related_artists.py +++ /dev/null @@ -1,108 +0,0 @@ -from sqlalchemy import text -from sqlalchemy.orm import Session - -from src.models.users.user import User -from src.queries.query_helpers import helpers, populate_user_metadata -from src.utils.db_session import get_db_read_replica -from src.utils.helpers import time_method - -_genre_based_sql = text( - """ - with inp as ( - select - genre, - count(*) as track_count, - rank() over (order by count(*) desc) as genre_rank - from - tracks t - where - t.is_current is true - and t.is_delete is false - and t.is_unlisted is false - and t.is_available is true - and t.stem_of is null - and owner_id = :user_id - group by - genre - order by count(*) desc limit 5 - ) - - -- find similar aritst based on similar top genre + follower count - select - user_id, - dominant_genre, - follower_count, - genre_rank - from - aggregate_user au - join inp on dominant_genre = inp.genre and au.follower_count < (select follower_count * 3 from aggregate_user where user_id = :user_id) - where user_id != :user_id - and ( - :filter_followed = false - or :current_user_id is null - or not exists ( - select 1 from follows f - where f.is_current = true - and f.is_delete = false - and f.follower_user_id = :current_user_id - and f.followee_user_id = au.user_id - ) - ) - order by genre_rank asc, follower_count desc - - limit :limit - offset :offset -""" -) - - -def _genre_based_related_artists( - session: Session, - user_id: int, - current_user_id: int, - limit=100, - offset=0, - filter_followed=False, -): - result = session.execute( - _genre_based_sql, - { - "user_id": user_id, - "current_user_id": current_user_id, - "filter_followed": filter_followed, - "limit": limit, - "offset": offset, - }, - ).fetchall() - user_ids = [r["user_id"] for r in result] - - # Get all users in a single query - users_query = session.query(User).filter(User.user_id.in_(user_ids)) - users = users_query.all() - - # Convert to list and create a map of user_id -> user - users_list = helpers.query_result_to_list(users) - users_map = {user["user_id"]: user for user in users_list} - - # Preserve order from original query by mapping user_ids back to users - ordered_users = [users_map[user_id] for user_id in user_ids] - return ordered_users - - -@time_method -def get_related_artists( - user_id: int, - current_user_id: int, - limit: int = 100, - offset: int = 0, - filter_followed: bool = False, -): - db = get_db_read_replica() - users = [] - with db.scoped_session() as session: - users = _genre_based_related_artists( - session, user_id, current_user_id, limit, offset, filter_followed - ) - user_ids = list(map(lambda user: user["user_id"], users)) - users = populate_user_metadata(session, user_ids, users, current_user_id) - return users diff --git a/packages/discovery-provider/src/queries/get_remixable_tracks.py b/packages/discovery-provider/src/queries/get_remixable_tracks.py deleted file mode 100644 index 59c8cc1d91e..00000000000 --- a/packages/discovery-provider/src/queries/get_remixable_tracks.py +++ /dev/null @@ -1,90 +0,0 @@ -from sqlalchemy import desc -from sqlalchemy.orm import aliased - -from src.models.tracks.aggregate_track import AggregateTrack -from src.models.tracks.stem import Stem -from src.models.tracks.track import Track -from src.queries.query_helpers import ( - add_users_to_tracks, - decayed_score, - populate_track_metadata, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -def get_remixable_tracks(args): - """Gets a list of remixable tracks""" - db = get_db_read_replica() - limit = args.get("limit", 25) - current_user_id = args.get("current_user_id", None) - - StemTrack = aliased(Track) - - with db.scoped_session() as session: - # Subquery to get current tracks that have stems - remixable_tracks_subquery = ( - session.query(Track) - .join(Stem, Stem.parent_track_id == Track.track_id) - .join(StemTrack, Stem.child_track_id == StemTrack.track_id) - .filter( - Track.is_current == True, - Track.is_unlisted == False, - Track.is_delete == False, - Track.is_stream_gated == False, - StemTrack.is_current == True, - StemTrack.is_unlisted == False, - StemTrack.is_delete == False, - ) - .distinct(Track.track_id) - .subquery() - ) - track_alias = aliased(Track, remixable_tracks_subquery) - - count_subquery = session.query( - AggregateTrack.track_id.label("id"), - (AggregateTrack.repost_count + AggregateTrack.save_count).label("count"), - ).subquery() - - query = ( - session.query( - track_alias, - count_subquery.c["count"], - decayed_score(count_subquery.c["count"], track_alias.created_at).label( - "score" - ), - ) - .join( - count_subquery, - count_subquery.c["id"] == track_alias.track_id, - ) - .order_by(desc("score"), desc(track_alias.track_id)) - .limit(limit) - ) - - results = query.all() - - tracks = [] - for result in results: - track = result[0] - # Convert decimal to float for serialization - score = float(result[-1]) - track = helpers.model_to_dictionary(track) - track["score"] = score - tracks.append(track) - - track_ids = list(map(lambda track: track["track_id"], tracks)) - - # Get user specific data for tracks - tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) - - if args.get("with_users", False): - add_users_to_tracks(session, tracks, current_user_id) - else: - # Remove the user from the tracks - tracks = [ - {key: val for key, val in dict.items() if key != "user"} - for dict in tracks - ] - - return tracks diff --git a/packages/discovery-provider/src/queries/get_remixes_of.py b/packages/discovery-provider/src/queries/get_remixes_of.py deleted file mode 100644 index 8ca976421be..00000000000 --- a/packages/discovery-provider/src/queries/get_remixes_of.py +++ /dev/null @@ -1,159 +0,0 @@ -from sqlalchemy import and_, desc, func, or_ -from sqlalchemy.orm import aliased - -from src import exceptions -from src.models.events.event import Event, EventType -from src.models.social.aggregate_plays import AggregatePlay -from src.models.social.repost import Repost, RepostType -from src.models.social.save import Save, SaveType -from src.models.tracks.aggregate_track import AggregateTrack -from src.models.tracks.remix import Remix -from src.models.tracks.track import Track -from src.queries.get_unpopulated_tracks import get_unpopulated_tracks -from src.queries.query_helpers import ( - RemixesSortMethod, - add_query_pagination, - add_users_to_tracks, - populate_track_metadata, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - -UNPOPULATED_REMIXES_CACHE_DURATION_SEC = 10 - - -def get_remixes_of(args): - track_id = args.get("track_id") - current_user_id = args.get("current_user_id") - limit, offset = args.get("limit"), args.get("offset") - sort_method = args.get("sort_method", "recent") - only_cosigns = args.get("only_cosigns", False) - only_contest_entries = args.get("only_contest_entries", False) - - db = get_db_read_replica() - - with db.scoped_session() as session: - - def get_unpopulated_remixes(): - # Fetch the parent track to get the track's owner id - parent_track_res = get_unpopulated_tracks( - session=session, - track_ids=[track_id], - filter_deleted=False, - filter_unlisted=False, - ) - - if not parent_track_res or parent_track_res[0] is None: - raise exceptions.ArgumentError("Invalid track_id provided") - - parent_track = parent_track_res[0] - track_owner_id = parent_track["owner_id"] - - # Get the 'children' remix tracks - # Use the track owner id to fetch reposted/saved tracks returned first - ParentTrack = aliased(Track) - - # Create a subquery to get only the most recent event per track - # This prevents duplicate rows when a track has multiple remix contest events - DistinctEvent = aliased( - session.query(Event) - .filter( - Event.event_type == EventType.remix_contest, - Event.is_deleted == False, - ) - .distinct(Event.entity_id) - .order_by(Event.entity_id, desc(Event.created_at)) - .subquery() - ) - - base_query = ( - session.query(Track) - .join( - Remix, - and_( - Remix.child_track_id == Track.track_id, - Remix.parent_track_id == track_id, - ), - ) - .outerjoin( - Save, - and_( - Save.save_item_id == Track.track_id, - Save.save_type == SaveType.track, - Save.is_current == True, - Save.is_delete == False, - Save.user_id == track_owner_id, - ), - ) - .outerjoin( - Repost, - and_( - Repost.repost_item_id == Track.track_id, - Repost.user_id == track_owner_id, - Repost.repost_type == RepostType.track, - Repost.is_current == True, - Repost.is_delete == False, - ), - ) - .outerjoin( - AggregateTrack, - AggregateTrack.track_id == Track.track_id, - ) - .outerjoin( - AggregatePlay, - AggregatePlay.play_item_id == Track.track_id, - ) - .outerjoin(ParentTrack, ParentTrack.track_id == Remix.parent_track_id) - .outerjoin( - DistinctEvent, - DistinctEvent.c.entity_id == ParentTrack.track_id, - ) - .filter( - Track.is_current == True, - Track.is_delete == False, - Track.is_unlisted == False, - ) - ) - if only_cosigns: - base_query = base_query.filter( - or_( - ParentTrack.owner_id == Save.user_id, - ParentTrack.owner_id == Repost.user_id, - ) - ) - if only_contest_entries: - base_query = base_query.filter( - and_( - DistinctEvent.c.created_at <= Track.created_at, - Track.created_at <= DistinctEvent.c.end_date, - ) - ) - - if sort_method == RemixesSortMethod.recent: - base_query = base_query.order_by( - desc(Track.created_at), desc(Track.track_id) - ) - elif sort_method == RemixesSortMethod.likes: - base_query = base_query.order_by( - desc(func.coalesce(AggregateTrack.save_count, 0)), - desc(Track.track_id), - ) - elif sort_method == RemixesSortMethod.plays: - base_query = base_query.order_by( - desc(func.coalesce(AggregatePlay.count, 0)), desc(Track.track_id) - ) - - (tracks, count) = add_query_pagination( - base_query, limit, offset, True, True - ) - tracks = tracks.all() - tracks = helpers.query_result_to_list(tracks) - track_ids = list(map(lambda track: track["track_id"], tracks)) - return (tracks, track_ids, count) - - (tracks, track_ids, count) = get_unpopulated_remixes() - tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) - if args.get("with_users", False): - add_users_to_tracks(session, tracks, current_user_id) - - return {"tracks": tracks, "count": count} diff --git a/packages/discovery-provider/src/queries/get_repost_feed_for_user.py b/packages/discovery-provider/src/queries/get_repost_feed_for_user.py deleted file mode 100644 index f24e4c511c8..00000000000 --- a/packages/discovery-provider/src/queries/get_repost_feed_for_user.py +++ /dev/null @@ -1,178 +0,0 @@ -from typing import Optional, TypedDict, cast - -from sqlalchemy import desc -from sqlalchemy.orm.session import Session -from sqlalchemy.sql.elements import and_, or_ - -from src.models.playlists.playlist import Playlist -from src.models.social.repost import Repost, RepostType -from src.models.social.save import SaveType -from src.models.tracks.track import Track -from src.models.users.user import User -from src.queries import response_name_constants -from src.queries.query_helpers import ( - add_query_pagination, - get_users_by_id, - get_users_ids, - populate_playlist_metadata, - populate_track_metadata, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -class GetRepostFeedForUserArgs(TypedDict): - offset: int - limit: int - handle: Optional[str] - current_user_id: Optional[int] - with_suers: Optional[bool] - - -def get_repost_feed_for_user(user_id: int, args: GetRepostFeedForUserArgs): - """ - Gets the repost feed for a user (e.g. stalking a user) - - Args: - user_id: number The user id to request the repost feed for - args: GetRepostFeedForUserArgs The parsed args from the request - - Returns: - Array of tracks and playlists (albums) interspersed ordered by - most recent repost - """ - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_repost_feed_for_user(session, user_id, args) - - -def _get_repost_feed_for_user( - session: Session, user_id: int, args: GetRepostFeedForUserArgs -): - feed_results = [] - current_user_id = args.get("current_user_id") - limit = args.get("limit") - offset = args.get("offset") - if "handle" in args: - handle = args.get("handle") or "" - user_id = cast( - int, - session.query(User.user_id) - .filter(User.handle_lc == handle.lower()) - .first(), - ) - - # Query all reposts by a user. - # Outerjoin both tracks and playlists to collect both - # so that a single limit/offset pagination does what we intend when tracks or playlists - # are deleted. - repost_query = ( - session.query(Repost, Track, Playlist) - .outerjoin( - Track, - and_( - Repost.repost_item_id == Track.track_id, - Repost.repost_type == "track", - Track.is_current == True, - Track.is_delete == False, - Track.is_unlisted == False, - Track.stem_of == None, - ), - ) - .outerjoin( - Playlist, - and_( - Repost.repost_item_id == Playlist.playlist_id, - or_(Repost.repost_type == "playlist", Repost.repost_type == "album"), - Playlist.is_current == True, - Playlist.is_delete == False, - Playlist.is_private == False, - ), - ) - .filter( - Repost.is_current == True, - Repost.is_delete == False, - Repost.user_id == user_id, - # Drop rows that have no join found for either track or playlist - or_(Track.track_id != None, Playlist.playlist_id != None), - ) - .order_by( - desc(Repost.created_at), - desc(Repost.repost_item_id), - desc(Repost.repost_type), - ) - ) - - reposts = add_query_pagination(repost_query, limit, offset).all() - # get track reposts from above - track_reposts = [r[0] for r in reposts if r[1] is not None] - track_reposts = helpers.query_result_to_list(track_reposts) - - # get playlist reposts from above - playlist_reposts = [r[0] for r in reposts if r[2] is not None] - playlist_reposts = helpers.query_result_to_list(playlist_reposts) - - # build track/playlist id --> repost dict from repost lists - track_repost_dict = {repost["repost_item_id"]: repost for repost in track_reposts} - playlist_repost_dict = { - repost["repost_item_id"]: repost for repost in playlist_reposts - } - - tracks = helpers.query_result_to_list( - filter(None, [repost[1] for repost in reposts]) - ) - playlists = helpers.query_result_to_list( - filter(None, [repost[2] for repost in reposts]) - ) - - # get track ids - track_ids = [track["track_id"] for track in tracks] - - # get playlist ids - playlist_ids = [playlist["playlist_id"] for playlist in playlists] - - # populate full metadata - tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) - playlists = populate_playlist_metadata( - session, - playlist_ids, - playlists, - [RepostType.playlist, RepostType.album], - [SaveType.playlist, SaveType.album], - current_user_id, - ) - - # add activity timestamps - for track in tracks: - track[response_name_constants.activity_timestamp] = track_repost_dict[ - track["track_id"] - ]["created_at"] - - for playlist in playlists: - playlist[response_name_constants.activity_timestamp] = playlist_repost_dict[ - playlist["playlist_id"] - ]["created_at"] - - unsorted_feed = tracks + playlists - - # sort feed by repost timestamp desc - feed_results = sorted( - unsorted_feed, - key=lambda entry: entry[response_name_constants.activity_timestamp], - reverse=True, - ) - - if args.get("with_users", False): - user_id_list = get_users_ids(feed_results) - users = get_users_by_id(session, user_id_list, current_user_id=current_user_id) - for result in feed_results: - if "playlist_owner_id" in result: - user = users[result["playlist_owner_id"]] - if user: - result["user"] = user - elif "owner_id" in result: - user = users[result["owner_id"]] - if user: - result["user"] = user - - return feed_results diff --git a/packages/discovery-provider/src/queries/get_tips.py b/packages/discovery-provider/src/queries/get_tips.py deleted file mode 100644 index f0296078ef0..00000000000 --- a/packages/discovery-provider/src/queries/get_tips.py +++ /dev/null @@ -1,339 +0,0 @@ -import logging -from datetime import datetime -from typing import List, Tuple, TypedDict, Union, cast - -from sqlalchemy import func, or_ -from sqlalchemy.orm import Query, aliased -from sqlalchemy.orm.session import Session - -from src.models.social.follow import Follow -from src.models.users.aggregate_user import AggregateUser -from src.models.users.aggregate_user_tips import AggregateUserTip -from src.models.users.user import User -from src.models.users.user_tip import UserTip -from src.queries.get_unpopulated_users import get_unpopulated_users -from src.queries.query_helpers import paginate_query, populate_user_metadata -from src.utils.db_session import get_db - -logger = logging.getLogger(__name__) - - -class GetTipsArgs(TypedDict): - limit: int - offset: int - user_id: int - receiver_min_followers: int - receiver_is_verified: bool - current_user_follows: str - unique_by: str - min_slot: int - max_slot: int - tx_signatures: List[int] - exclude_recipients: List[int] - - -class TipResult(TypedDict): - amount: int - sender: str - receiver: str - slot: int - created_at: datetime - followee_supporters: List[str] - tx_signature: str - - -class PopulatedTipResult(TypedDict): - amount: int - sender: User - receiver: User - slot: int - created_at: datetime - followee_supporters: List[str] - tx_signature: str - - -# Example of query with inputs: -# limit=100 -# offset=0 -# user_id=8 -# current_user_follows="sender_or_receiver" -# receiver_is_verified=True, -# receiver_min_followers=10 -# min_slot=185524 -# max_slot=185673 -# --------------------------------------------------------- -# WITH followees AS -# ( -# SELECT -# follows.followee_user_id AS followee_user_id -# FROM -# follows -# WHERE -# follows.is_current = true -# AND follows.is_delete = false -# AND follows.follower_user_id = 8 -# ) -# , -# tips AS -# ( -# SELECT -# user_tips_1.signature AS signature, -# user_tips_1.slot AS slot, -# user_tips_1.sender_user_id AS sender_user_id, -# user_tips_1.receiver_user_id AS receiver_user_id, -# user_tips_1.amount AS amount, -# user_tips_1.created_at AS created_at, -# user_tips_1.updated_at AS updated_at -# FROM -# user_tips AS user_tips_1 -# JOIN -# aggregate_user -# ON aggregate_user.user_id = user_tips_1.receiver_user_id -# JOIN -# users -# ON users.user_id = user_tips_1.receiver_user_id -# LEFT OUTER JOIN -# followees AS followees_for_sender -# ON user_tips_1.sender_user_id = followees_for_sender.followee_user_id -# LEFT OUTER JOIN -# followees AS followees_for_receiver -# ON user_tips_1.receiver_user_id = followees_for_receiver.followee_user_id -# WHERE -# aggregate_user.follower_count >= 10 -# AND users.is_current = true -# AND users.is_verified = true -# AND user_tips_1.slot >= 185524 -# AND user_tips_1.slot <= 185673 -# AND -# ( -# followees_for_sender.followee_user_id IS NOT NULL -# OR followees_for_receiver.followee_user_id IS NOT NULL -# ) -# ORDER BY -# user_tips_1.slot DESC LIMIT 100 OFFSET 0 -# ) -# , -# followee_tippers AS -# ( -# SELECT -# aggregate_user_tips.sender_user_id AS sender_user_id, -# aggregate_user_tips.receiver_user_id AS receiver_user_id, -# followees_for_aggregate.followee_user_id AS followee_user_id -# FROM -# followees AS followees_for_aggregate -# LEFT OUTER JOIN -# aggregate_user_tips -# ON aggregate_user_tips.sender_user_id = followees_for_aggregate.followee_user_id -# ) -# SELECT -# tips.signature, -# tips.slot, -# tips.sender_user_id, -# tips.receiver_user_id, -# tips.amount, -# tips.created_at, -# tips.updated_at, -# array_agg(followee_tippers.sender_user_id) AS array_agg_1 -# FROM -# tips -# LEFT OUTER JOIN -# followee_tippers -# ON followee_tippers.receiver_user_id = tips.receiver_user_id -# GROUP BY -# tips.signature, -# tips.slot, -# tips.sender_user_id, -# tips.receiver_user_id, -# tips.amount, -# tips.created_at, -# tips.updated_at -# ORDER BY -# tips.slot DESC - - -def _get_tips(session: Session, args: GetTipsArgs): - UserTipAlias = aliased(UserTip) - query: Query = session.query(UserTipAlias) - has_pagination = False # Keeps track if we already paginated - - if args.get("exclude_recipients"): - query = query.filter( - UserTipAlias.receiver_user_id.notin_(args["exclude_recipients"]) - ) - - if args.get("tx_signatures"): - query = query.filter(UserTipAlias.signature.in_(args["tx_signatures"])) - if args.get("receiver_min_followers", 0) > 0: - query = query.join( - AggregateUser, AggregateUser.user_id == UserTipAlias.receiver_user_id - ).filter(AggregateUser.follower_count >= args["receiver_min_followers"]) - - if args.get("receiver_is_verified", False): - query = query.join(User, User.user_id == UserTipAlias.receiver_user_id).filter( - User.is_current == True, User.is_verified == True - ) - if args.get("min_slot", 0) > 0: - query = query.filter(UserTipAlias.slot >= args["min_slot"]) - if args.get("max_slot", 0) > 0: - query = query.filter(UserTipAlias.slot <= args["max_slot"]) - if args.get("unique_by"): - if args["unique_by"] == "sender": - distinct_inner = ( - query.order_by( - UserTipAlias.sender_user_id.asc(), UserTipAlias.slot.desc() - ) - .distinct(UserTipAlias.sender_user_id) - .subquery() - ) - UserTipAlias = aliased(UserTip, distinct_inner, name="user_tips_uniqued") - query = session.query(UserTipAlias) - elif args["unique_by"] == "receiver": - distinct_inner = ( - query.order_by( - UserTipAlias.receiver_user_id.asc(), UserTipAlias.slot.desc() - ) - .distinct(UserTipAlias.receiver_user_id) - .subquery() - ) - UserTipAlias = aliased(UserTip, distinct_inner, name="user_tips_uniqued") - query = session.query(UserTipAlias) - - if args.get("user_id"): - # We have to get the other users that this user follows for three potential uses: - # 1) To filter tips to recipients the user follows (if necessary) - # 2) To filter tips to senders the user follows (if necessary) - # 3) To get the followees of the current user that have also tipped the receiver - followees_query = ( - session.query(Follow.followee_user_id) - .filter( - Follow.is_current == True, - Follow.is_delete == False, - Follow.follower_user_id == args["user_id"], - ) - .cte("followees") - ) - # First, filter the senders/receivers as necessary - if args.get("current_user_follows"): - FolloweesSender = aliased(followees_query, name="followees_for_sender") - FolloweesReceiver = aliased(followees_query, name="followees_for_receiver") - if args["current_user_follows"] == "receiver": - query = query.join( - FolloweesReceiver, - UserTipAlias.receiver_user_id - == FolloweesReceiver.c.followee_user_id, - ) - elif args["current_user_follows"] == "sender": - query = query.join( - FolloweesSender, - UserTipAlias.receiver_user_id == FolloweesSender.c.followee_user_id, - ) - elif args["current_user_follows"] == "sender_or_receiver": - query = query.outerjoin( - FolloweesSender, - UserTipAlias.sender_user_id == FolloweesSender.c.followee_user_id, - ) - query = query.outerjoin( - FolloweesReceiver, - UserTipAlias.receiver_user_id - == FolloweesReceiver.c.followee_user_id, - ) - query = query.filter( - or_( - FolloweesSender.c.followee_user_id != None, - FolloweesReceiver.c.followee_user_id != None, - ) - ) - - # Order and paginate before adding follower filters/aggregates - query = query.order_by(UserTipAlias.slot.desc()) - query = paginate_query(query) - - # Get the tips for the user as a subquery - # because now we need to get the other users that tipped that receiver - # and joining on this already paginated/limited result will be much faster - Tips = query.cte("tips") - FolloweesAggregate = aliased(followees_query, name="followees_for_aggregate") - - # Get all of the followees joined on their aggregate user tips first - # rather than joining each on the tips separately to help with speed - FolloweeTippers = ( - session.query( - AggregateUserTip.sender_user_id, - AggregateUserTip.receiver_user_id, - FolloweesAggregate.c.followee_user_id, - ) - .select_from(FolloweesAggregate) - .outerjoin( - AggregateUserTip, - AggregateUserTip.sender_user_id - == FolloweesAggregate.c.followee_user_id, - ) - .cte("followee_tippers") - ) - # Now we have the tips listed multiple times, one for each followee sender. - # So group by the tip and aggregate up the followee sender IDs into a list - query = ( - session.query(UserTip, func.array_agg(FolloweeTippers.c.sender_user_id)) - .select_entity_from(Tips) - .outerjoin( - FolloweeTippers, - FolloweeTippers.c.receiver_user_id == Tips.c.receiver_user_id, - ) - .group_by(Tips) - ) - has_pagination = True - - query = query.order_by(UserTipAlias.slot.desc()) - if not has_pagination: - query = paginate_query(query) - - tips_results: List[UserTip] = query.all() - return tips_results - - -def get_tips(args: GetTipsArgs, db=None) -> List[PopulatedTipResult]: - db = get_db() - with db.scoped_session() as session: - results: Union[List[Tuple[UserTip, List[str]]], List[UserTip]] = _get_tips( - session, args - ) - tips_results: List[Tuple[UserTip, List[str]]] = [] - # Wrap in tuple for consistency - if results and isinstance(results[0], UserTip): - tips_results = [(cast(UserTip, tip), []) for tip in results] - else: - # MyPy doesn't seem smart enough to figure this out, help it with a cast - tips_results = cast(List[Tuple[UserTip, List[str]]], results) - - # Collect user IDs and fetch users - user_ids = set() - for result in tips_results: - user_ids.add(result[0].sender_user_id) - user_ids.add(result[0].receiver_user_id) - users = get_unpopulated_users(session, user_ids) - users = populate_user_metadata( - session, user_ids, users, args["user_id"] if "user_id" in args else None - ) - users_map = {} - for user in users: - users_map[user["user_id"]] = user - - # Not using model_to_dictionary() here because TypedDict complains about dynamic keys - tips: List[PopulatedTipResult] = [ - { - "amount": result[0].amount, - "sender": users_map[result[0].sender_user_id], - "receiver": users_map[result[0].receiver_user_id], - "followee_supporters": list( - filter( - lambda id: id is not None, - result[1], - ) - ), - "slot": result[0].slot, - "created_at": result[0].created_at, - "tx_signature": result[0].signature, - } - for result in tips_results - ] - return tips diff --git a/packages/discovery-provider/src/queries/get_top_genre_users.py b/packages/discovery-provider/src/queries/get_top_genre_users.py deleted file mode 100644 index 68b534a5a34..00000000000 --- a/packages/discovery-provider/src/queries/get_top_genre_users.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging - -from sqlalchemy import asc, desc - -from src.models.users.aggregate_user import AggregateUser -from src.models.users.user import User -from src.queries.query_helpers import add_query_pagination, populate_user_metadata -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - -logger = logging.getLogger(__name__) - - -def get_top_genre_users(args): - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_top_genre_users(session, args) - - -def _get_top_genre_users(session, args): - genres = [] - if "genre" in args: - genres = args.get("genre") - if isinstance(genres, str): - genres = [genres] - - with_users = args.get("with_users", True) - limit = args.get("limit", 10) - offset = args.get("offset", 0) - - top_users_query = ( - session.query(User) - .join(AggregateUser, User.user_id == AggregateUser.user_id) - .filter( - AggregateUser.dominant_genre.in_(genres), - User.is_deactivated == False, - User.is_available == True, - ) - .order_by(desc(AggregateUser.follower_count), asc(User.user_id)) - ) - users = add_query_pagination(top_users_query, limit, offset).all() - users = helpers.query_result_to_list(users) - user_ids = list(map(lambda user: user["user_id"], users)) - - if with_users: - users = populate_user_metadata(session, user_ids, users, None) - - # Sort the users so that it's in the same order as the previous query - user_map = {user["user_id"]: user for user in users} - users = [user_map[user_id] for user_id in user_ids] - return users - - return user_ids diff --git a/packages/discovery-provider/src/queries/get_top_listeners_for_track.py b/packages/discovery-provider/src/queries/get_top_listeners_for_track.py deleted file mode 100644 index c34d0346f9c..00000000000 --- a/packages/discovery-provider/src/queries/get_top_listeners_for_track.py +++ /dev/null @@ -1,69 +0,0 @@ -from sqlalchemy import text - -from src import exceptions -from src.models.tracks.track import Track -from src.queries.get_unpopulated_users import get_unpopulated_users -from src.queries.query_helpers import populate_user_metadata -from src.utils.db_session import get_db_read_replica - - -def get_top_listeners_for_track(args): - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_top_listeners_for_track(session, args) - - -def _get_top_listeners_for_track(session, args): - users_with_count = [] - current_user_id = args.get("current_user_id") - track_id = args.get("track_id") - limit = args.get("limit") - offset = args.get("offset") - - # Ensure Track exists for provided track_id. - track_entry = ( - session.query(Track) - .filter(Track.track_id == track_id, Track.is_current == True) - .first() - ) - if track_entry is None: - raise exceptions.NotFoundError("Resource not found for provided track id") - - cid_source_res = text( - """ - with - deduped as ( - select distinct play_item_id, user_id, date_trunc('hour', created_at) as created_at - from plays - where user_id is not null - and play_item_id = :track_id - ), - counted as ( - select user_id, count(*) as play_count - from deduped - group by 1 - ) - select * - from counted - left join aggregate_user using (user_id) - order by play_count desc, follower_count desc, counted.user_id asc - limit :limit - offset :offset - """ - ) - top_listener_rows = session.execute( - cid_source_res, {"track_id": track_id, "limit": limit, "offset": offset} - ).fetchall() - - top_listeners = {r["user_id"]: r["play_count"] for r in top_listener_rows} - - # Fix format to return only Users objects with follower_count field. - user_ids = top_listeners.keys() - users = get_unpopulated_users(session, user_ids) - users = populate_user_metadata(session, user_ids, users, current_user_id) - - users_with_count = [ - {"user": u, "count": top_listeners[u["user_id"]]} for u in users - ] - - return users_with_count diff --git a/packages/discovery-provider/src/queries/get_top_user_track_tags.py b/packages/discovery-provider/src/queries/get_top_user_track_tags.py deleted file mode 100644 index 08fdd0e1d02..00000000000 --- a/packages/discovery-provider/src/queries/get_top_user_track_tags.py +++ /dev/null @@ -1,45 +0,0 @@ -import logging # pylint: disable=C0302 - -from sqlalchemy import desc, func - -from src.models.tracks.tag_track_user_matview import t_tag_track_user -from src.models.tracks.track import Track -from src.utils.db_session import get_db_read_replica - -logger = logging.getLogger(__name__) - - -def get_top_user_track_tags(args): - """ - Gets the most used tags for tracks owned by the query user - - Args: - args: dict The parsed args from the request - args.limit: number optional The max number of tags to return - args.user_id: number The user id used to query for tracks - - Returns: - Array of strings ordered by most used tag in track - """ - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_top_user_track_tags(session, args) - - -def _get_top_user_track_tags(session, args): - most_used_tags = ( - session.query(t_tag_track_user.c.tag) - .join(Track, Track.track_id == t_tag_track_user.c.track_id) - .filter(t_tag_track_user.c.owner_id == args["user_id"]) - .filter(Track.is_delete == False) - .group_by(t_tag_track_user.c.tag) - .order_by( - desc(func.count(t_tag_track_user.c.tag)), desc(t_tag_track_user.c.tag) - ) - .all() - ) - - tags = [tag for tag, in most_used_tags] - if "limit" in args: - return tags[: args["limit"]] - return tags diff --git a/packages/discovery-provider/src/queries/get_top_users.py b/packages/discovery-provider/src/queries/get_top_users.py deleted file mode 100644 index e25e4a93c17..00000000000 --- a/packages/discovery-provider/src/queries/get_top_users.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy import text - -from src.queries.query_helpers import get_pagination_vars, populate_user_metadata -from src.utils.db_session import get_db_read_replica - -sql = """ -select users.* -from users -join aggregate_user using (user_id) -where - is_current - and user_id in ( - select user_id from aggregate_user - where track_count > 0 - order by follower_count desc, user_id asc - limit :limit - offset :offset - ) -order by follower_count desc, user_id asc; -""" - - -def get_top_users(current_user_id): - """Gets the top users by follows of all of Audius""" - (limit, offset) = get_pagination_vars() - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_top_users(session, current_user_id, limit, offset) - - -def _get_top_users(session, current_user_id, limit, offset): - top_users = session.execute(text(sql), {"limit": limit, "offset": offset}) - top_users = [dict(row) for row in top_users] - user_ids = list(map(lambda user: user["user_id"], top_users)) - top_users = populate_user_metadata(session, user_ids, top_users, current_user_id) - return top_users diff --git a/packages/discovery-provider/src/queries/get_total_plays.py b/packages/discovery-provider/src/queries/get_total_plays.py deleted file mode 100644 index 78361a73e97..00000000000 --- a/packages/discovery-provider/src/queries/get_total_plays.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import func - -from src.models.social.aggregate_plays import AggregatePlay -from src.utils.db_session import get_db_read_replica - - -def get_total_plays(): - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_total_plays(session) - - -def _get_total_plays(session): - """Gets the total number of plays across all tracks - - Args: - session: SQLAlchemy session - - Returns: - int: Total number of plays - """ - total_plays = session.query( - func.sum(func.coalesce(AggregatePlay.count, 0)) - ).scalar() - - return int(total_plays) if total_plays else 0 diff --git a/packages/discovery-provider/src/queries/get_trailing_metrics.py b/packages/discovery-provider/src/queries/get_trailing_metrics.py deleted file mode 100644 index 322ec7a4287..00000000000 --- a/packages/discovery-provider/src/queries/get_trailing_metrics.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -from datetime import date, timedelta - -from sqlalchemy import func - -from src.models.metrics.aggregate_daily_total_users_metrics import ( - AggregateDailyTotalUsersMetrics, -) -from src.models.metrics.aggregate_daily_unique_users_metrics import ( - AggregateDailyUniqueUsersMetrics, -) -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -def get_aggregate_route_metrics_trailing(time_range): - """ - Returns trailing count and unique count for all routes in the last trailing month or year - - Accepts: time_range = 'month' or 'year' or 'week' - Returns: - { unique_count, total_count } - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_aggregate_route_metrics_trailing(session, time_range or "month") - - -def _get_aggregate_route_metrics_trailing(session, time_range): - today = date.today() - if time_range == "year": - days_ago = today - timedelta(days=365) - elif time_range == "week": - days_ago = today - timedelta(days=7) - else: - days_ago = today - timedelta(days=30) - - counts = ( - session.query( - func.sum(AggregateDailyUniqueUsersMetrics.count), - func.sum(AggregateDailyUniqueUsersMetrics.summed_count), - ) - .filter(days_ago <= AggregateDailyUniqueUsersMetrics.timestamp) - .filter(AggregateDailyUniqueUsersMetrics.timestamp < today) - .first() - ) - - total_count = ( - session.query(func.sum(AggregateDailyTotalUsersMetrics.count)) - .filter(days_ago <= AggregateDailyTotalUsersMetrics.timestamp) - .filter(AggregateDailyTotalUsersMetrics.timestamp < today) - .first() - ) - - return { - "unique_count": counts[0], - "summed_unique_count": counts[1], - "total_count": total_count[0], - } diff --git a/packages/discovery-provider/src/queries/get_undisbursed_challenges.py b/packages/discovery-provider/src/queries/get_undisbursed_challenges.py deleted file mode 100644 index 559b970a457..00000000000 --- a/packages/discovery-provider/src/queries/get_undisbursed_challenges.py +++ /dev/null @@ -1,128 +0,0 @@ -from typing import List, Optional, Tuple, TypedDict - -from sqlalchemy import and_, asc - -from src.models.rewards.challenge import Challenge -from src.models.rewards.challenge_disbursement import ChallengeDisbursement -from src.models.rewards.user_challenge import UserChallenge -from src.models.users.user import User - - -class UndisbursedChallengeResponse(TypedDict): - challenge_id: str - user_id: int - specifier: str - amount: str - completed_blocknumber: Optional[int] - handle: str - wallet: str - created_at: str - completed_at: str - cooldown_days: Optional[int] - - -def to_challenge_response( - user_challenge: UserChallenge, - challenge: Challenge, - handle: str, - wallet: str, -) -> UndisbursedChallengeResponse: - return { - "challenge_id": challenge.id, - "user_id": user_challenge.user_id, - "specifier": user_challenge.specifier, - "amount": str(user_challenge.amount), - "completed_blocknumber": user_challenge.completed_blocknumber, - "handle": handle, - "wallet": wallet, - "created_at": str(user_challenge.created_at), - "completed_at": str(user_challenge.completed_at), - "cooldown_days": challenge.cooldown_days, - } - - -class UndisbursedChallengesArgs(TypedDict): - user_id: Optional[int] - limit: Optional[int] - offset: Optional[int] - completed_blocknumber: Optional[int] - challenge_id: Optional[str] - - -MAX_LIMIT = 500 -DEFAULT_LIMIT = 100 - - -# Gets undisbursed challenges -# returning a list of challenge responses -def get_undisbursed_challenges( - session, args: UndisbursedChallengesArgs -) -> List[UndisbursedChallengeResponse]: - undisbursed_challenges_query = ( - session.query(UserChallenge, Challenge, User.handle, User.wallet) - .outerjoin( - ChallengeDisbursement, - and_( - ChallengeDisbursement.specifier == UserChallenge.specifier, - ChallengeDisbursement.challenge_id == UserChallenge.challenge_id, - ChallengeDisbursement.user_id == UserChallenge.user_id, - ), - ) - .outerjoin( - Challenge, - Challenge.id == UserChallenge.challenge_id, - ) - .join(User, UserChallenge.user_id == User.user_id) - .filter( - # Check that there is no matching challenge disburstment - ChallengeDisbursement.challenge_id == None, - UserChallenge.is_complete == True, - Challenge.active == True, - User.is_current == True, - User.is_deactivated == False, - ) - .order_by( - asc(UserChallenge.completed_blocknumber), - asc(UserChallenge.user_id), - asc(UserChallenge.challenge_id), - ) - ) - - # Add Filters to the queries - - if args["user_id"] is not None: - undisbursed_challenges_query = undisbursed_challenges_query.filter( - UserChallenge.user_id == args["user_id"] - ) - - if args["completed_blocknumber"] is not None: - undisbursed_challenges_query = undisbursed_challenges_query.filter( - UserChallenge.completed_blocknumber > args["completed_blocknumber"] - ) - - if args["challenge_id"] is not None: - undisbursed_challenges_query = undisbursed_challenges_query.filter( - UserChallenge.challenge_id == args["challenge_id"] - ) - - limit = DEFAULT_LIMIT - - if args["limit"] is not None: - limit = min(MAX_LIMIT, args["limit"]) - undisbursed_challenges_query = undisbursed_challenges_query.limit(limit) - - if args["offset"] is not None: - undisbursed_challenges_query = undisbursed_challenges_query.offset( - args["offset"] - ) - - undisbursed_challenges: List[ - Tuple[UserChallenge, Challenge, str, str] - ] = undisbursed_challenges_query.all() - - undisbursed_challenges_response: List[UndisbursedChallengeResponse] = [ - to_challenge_response(user_challenge, challenge, handle, wallet) - for user_challenge, challenge, handle, wallet in undisbursed_challenges - ] - - return undisbursed_challenges_response diff --git a/packages/discovery-provider/src/queries/get_usdc_purchases.py b/packages/discovery-provider/src/queries/get_usdc_purchases.py deleted file mode 100644 index abe18e81d54..00000000000 --- a/packages/discovery-provider/src/queries/get_usdc_purchases.py +++ /dev/null @@ -1,150 +0,0 @@ -from typing import List, Optional, TypedDict - -from sqlalchemy import asc, desc, or_ - -from src.models.playlists.playlist import Playlist -from src.models.tracks.track import Track -from src.models.users.usdc_purchase import PurchaseType, USDCPurchase -from src.models.users.user import User -from src.queries.query_helpers import ( - PurchaseSortMethod, - SortDirection, - add_query_pagination, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -class GetUSDCPurchasesCountArgs(TypedDict): - seller_user_id: Optional[int] - buyer_user_id: Optional[int] - content_ids: Optional[List[int]] - content_type: Optional[PurchaseType] - - -class GetUSDCPurchasesArgs(TypedDict): - seller_user_id: Optional[int] - buyer_user_id: Optional[int] - content_ids: Optional[List[int]] - content_type: Optional[PurchaseType] - limit: int - offset: int - sort_method: Optional[PurchaseSortMethod] - sort_direction: Optional[SortDirection] - - -def _get_usdc_purchases(session, args: GetUSDCPurchasesCountArgs): - base_query = session.query(USDCPurchase) - buyer_user_id = args.get("buyer_user_id") - seller_user_id = args.get("seller_user_id") - content_ids = args.get("content_ids") - content_type = args.get("content_type") - sort_method = args.get("sort_method", None) - sort_direction = args.get("sort_direction", SortDirection.desc) - - # Basic filters - if content_ids: - base_query = base_query.filter(USDCPurchase.content_id.in_(content_ids)) - if buyer_user_id: - base_query = base_query.filter(USDCPurchase.buyer_user_id == buyer_user_id) - if seller_user_id: - base_query = base_query.filter(USDCPurchase.seller_user_id == seller_user_id) - if content_type: - base_query = base_query.filter(USDCPurchase.content_type == content_type) - - sort_fn = desc if sort_direction == SortDirection.desc else asc - if ( - sort_method == PurchaseSortMethod.artist_name - or sort_method == PurchaseSortMethod.content_title - ): - # If we're sorting by content related fields we need to join to get those fields - # First get the playlists - playlists_query = ( - base_query.filter( - or_( - USDCPurchase.content_type == PurchaseType.playlist, - USDCPurchase.content_type == PurchaseType.album, - ) - ) - .join(Playlist, Playlist.playlist_id == USDCPurchase.content_id) - .filter(Playlist.is_current == True) - .add_columns( - Playlist.playlist_name.label("content_title"), - Playlist.playlist_owner_id.label("owner_id"), - ) - ) - # Then the tracks - tracks_query = ( - base_query.filter(USDCPurchase.content_type == PurchaseType.track) - .join(Track, Track.track_id == USDCPurchase.content_id) - .filter(Track.is_current == True) - .add_columns( - Track.title.label("content_title"), - Track.owner_id.label("owner_id"), - ) - ) - if content_type == PurchaseType.track: - # Just tracks - subquery = tracks_query.subquery(name="track_subquery") - elif ( - content_type == PurchaseType.album or content_type == PurchaseType.playlist - ): - # Just playlists - subquery = playlists_query.subquery(name="playlist_subquery") - else: - # Union them together - subquery = playlists_query.union(tracks_query).subquery( - name="union_subquery" - ) - # Select the purchase entities from our subquery, sorting by the extra fields we added - if sort_method == PurchaseSortMethod.artist_name: - # Also get users as necessary - base_query = ( - session.query(USDCPurchase) - .select_entity_from(subquery) - .join(User, User.user_id == subquery.c.owner_id) - .filter(User.is_current == True) - .order_by(sort_fn(User.name), sort_fn(USDCPurchase.created_at)) - ) - elif sort_method == PurchaseSortMethod.content_title: - base_query = ( - session.query(USDCPurchase) - .select_entity_from(subquery) - .order_by( - sort_fn(subquery.c.content_title), - sort_fn(USDCPurchase.created_at), - ) - ) - elif sort_method == PurchaseSortMethod.buyer_name: - # Join users to get buyers and sort by username - base_query = ( - base_query.join(User, User.user_id == USDCPurchase.buyer_user_id) - .filter(User.is_current == True) - .order_by(sort_fn(User.name), sort_fn(USDCPurchase.created_at)) - ) - elif sort_method == PurchaseSortMethod.date: - base_query = base_query.order_by(sort_fn(USDCPurchase.created_at)) - - return base_query - - -def get_usdc_purchases_count(args: GetUSDCPurchasesCountArgs): - """Gets the count of all the USDC purchase entries fitting the given criteria.""" - db = get_db_read_replica() - with db.scoped_session() as session: - query = _get_usdc_purchases(session, args) - return query.count() - - -def get_usdc_purchases(args: GetUSDCPurchasesArgs): - """Gets all the USDC purchase entries fitting the given criteria. - - Does not include any other entity metadatas (no tracks, users, playlists).""" - db = get_db_read_replica() - with db.scoped_session() as session: - query = _get_usdc_purchases(session, args) - limit = args.get("limit") - offset = args.get("offset") - - results = add_query_pagination(query, limit, offset).all() - return helpers.query_result_to_list(results) diff --git a/packages/discovery-provider/src/queries/get_usdc_transactions_history.py b/packages/discovery-provider/src/queries/get_usdc_transactions_history.py deleted file mode 100644 index 76febf13d32..00000000000 --- a/packages/discovery-provider/src/queries/get_usdc_transactions_history.py +++ /dev/null @@ -1,155 +0,0 @@ -import logging -from typing import List, Optional, TypedDict - -from sqlalchemy import asc, desc -from sqlalchemy.orm import Query -from sqlalchemy.orm.session import Session - -from src.models.users.usdc_transactions_history import ( - USDCTransactionMethod, - USDCTransactionsHistory, - USDCTransactionType, -) -from src.models.users.user import User -from src.models.users.user_bank import USDCUserBankAccount -from src.queries.query_helpers import ( - SortDirection, - TransactionSortMethod, - paginate_query, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - -logger = logging.getLogger(__name__) - - -class GetUSDCTransactionsCountArgs(TypedDict): - user_id: int - transaction_type: Optional[List[USDCTransactionType]] - transaction_method: Optional[USDCTransactionMethod] - include_system_transactions: Optional[bool] - - -class GetUSDCTransactionsArgs(TypedDict): - user_id: int - sort_direction: SortDirection - sort_method: TransactionSortMethod - transaction_type: Optional[List[USDCTransactionType]] - transaction_method: Optional[USDCTransactionMethod] - include_system_transactions: Optional[bool] - limit: int - offset: int - - -USDC_SYSTEM_TRANSACTION_TYPES = [ - USDCTransactionType.prepare_withdrawal, - USDCTransactionType.recover_withdrawal, -] - - -# SELECT count(*) -# FROM users -# JOIN usdc_user_bank_accounts ON usdc_user_bank_accounts.ethereum_address = users.wallet -# JOIN usdc_transactions_history ON usdc_transactions_history.user_bank = usdc_user_bank_accounts.bank_account -# WHERE users.user_id = AND users.is_current = TRUE - - -def _get_usdc_transactions_history_count( - session: Session, args: GetUSDCTransactionsCountArgs -): - user_id = args.get("user_id") - transaction_type = args.get("transaction_type", None) - transaction_method = args.get("transaction_method", None) - include_system_transactions = args.get("include_system_transactions", False) - query: Query = ( - session.query(USDCTransactionsHistory) - .select_from(User) - .filter(User.user_id == user_id, User.is_current == True) - .join( - USDCUserBankAccount, - USDCUserBankAccount.ethereum_address == User.wallet, - ) - .join( - USDCTransactionsHistory, - USDCTransactionsHistory.user_bank == USDCUserBankAccount.bank_account, - ) - ) - if transaction_type is not None: - query = query.filter( - USDCTransactionsHistory.transaction_type.in_(transaction_type) - ) - if not include_system_transactions: - query = query.filter( - USDCTransactionsHistory.transaction_type.notin_( - USDC_SYSTEM_TRANSACTION_TYPES - ) - ) - if transaction_method is not None: - query = query.filter(USDCTransactionsHistory.method == transaction_method) - count: int = query.count() - return count - - -def get_usdc_transactions_history_count(args: GetUSDCTransactionsCountArgs): - db = get_db_read_replica() - with db.scoped_session() as session: - count = _get_usdc_transactions_history_count(session, args) - return count - - -# SELECT usdc_transactions_history.* -# FROM users -# JOIN usdc_user_bank_accounts ON usdc_user_bank_accounts.ethereum_address = users.wallet -# JOIN usdc_transactions_history ON usdc_transactions_history.user_bank = usdc_user_bank_accounts.bank_account -# WHERE users.user_id = AND users.is_current = TRUE -# ORDER BY usdc_transactions_history.transaction_created_at ASC - - -def _get_usdc_transactions_history(session: Session, args: GetUSDCTransactionsArgs): - transaction_type = args.get("transaction_type", None) - transaction_method = args.get("transaction_method", None) - sort_method = args.get("sort_method") - include_system_transactions = args.get("include_system_transactions", False) - sort_direction = args.get("sort_direction") - - query: Query = ( - session.query(USDCTransactionsHistory) - .select_from(User) - .filter(User.user_id == args["user_id"], User.is_current == True) - .join(USDCUserBankAccount, USDCUserBankAccount.ethereum_address == User.wallet) - .join( - USDCTransactionsHistory, - USDCTransactionsHistory.user_bank == USDCUserBankAccount.bank_account, - ) - ) - if transaction_type is not None: - query = query.filter( - USDCTransactionsHistory.transaction_type.in_(transaction_type) - ) - if not include_system_transactions: - query = query.filter( - USDCTransactionsHistory.transaction_type.notin_( - USDC_SYSTEM_TRANSACTION_TYPES - ) - ) - if transaction_method is not None: - query = query.filter(USDCTransactionsHistory.method == transaction_method) - - sort_fn = desc if sort_direction == SortDirection.desc else asc - if sort_method == TransactionSortMethod.date: - query = query.order_by(sort_fn(USDCTransactionsHistory.transaction_created_at)) - elif sort_method == TransactionSortMethod.transaction_type: - query = query.order_by( - sort_fn(USDCTransactionsHistory.transaction_type), - desc(USDCTransactionsHistory.transaction_created_at), - ) - query = paginate_query(query) - results: List[USDCTransactionsHistory] = query.all() - return results - - -def get_usdc_transactions_history(args: GetUSDCTransactionsArgs): - db = get_db_read_replica() - with db.scoped_session() as session: - history = _get_usdc_transactions_history(session, args) - return helpers.query_result_to_list(history) diff --git a/packages/discovery-provider/src/queries/get_user_listen_counts_monthly.py b/packages/discovery-provider/src/queries/get_user_listen_counts_monthly.py deleted file mode 100644 index 90919e0db3d..00000000000 --- a/packages/discovery-provider/src/queries/get_user_listen_counts_monthly.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import TypedDict - -from sqlalchemy import text -from sqlalchemy.orm.session import Session - -from src.utils.db_session import get_db_read_replica - - -class GetUserListenCountsMonthlyArgs(TypedDict): - # The current user logged in (from route param) - user_id: int - - # The start time from which to search (from query arg) - start_time: str - - # The end time until which to search (from query arg) - end_time: str - - -def get_user_listen_counts_monthly(args: GetUserListenCountsMonthlyArgs): - """ - Returns a user's listen counts for all tracks they own, grouped by month. - - Args: - args: GetUserListenCountsMonthlyArgs The parsed args from the request - - Returns: - Array of AggregateMonthlyPlay objects that are timestamped within the given - time frame and whose tracks all belong to the given user id. - """ - - db = get_db_read_replica() - with db.scoped_session() as session: - return list(_get_user_listen_counts_monthly(session, args)) - - -def _get_user_listen_counts_monthly( - session: Session, args: GetUserListenCountsMonthlyArgs -): - sql = text( - """ - select - play_item_id, - timestamp, - sum(count) as count - from aggregate_monthly_plays - where play_item_id in (select track_id from tracks where owner_id = :user_id) - and timestamp >= :start_time - and timestamp < :end_time - group by play_item_id, timestamp - """ - ) - - return session.execute(sql, args) diff --git a/packages/discovery-provider/src/queries/get_user_listening_history.py b/packages/discovery-provider/src/queries/get_user_listening_history.py deleted file mode 100644 index b12066a3394..00000000000 --- a/packages/discovery-provider/src/queries/get_user_listening_history.py +++ /dev/null @@ -1,180 +0,0 @@ -from typing import Optional, TypedDict - -from sqlalchemy import asc, desc, func, or_ -from sqlalchemy.orm.session import Session -from sqlalchemy.sql.functions import coalesce - -from src.models.social.aggregate_plays import AggregatePlay -from src.models.tracks.aggregate_track import AggregateTrack -from src.models.tracks.track_with_aggregates import TrackWithAggregates -from src.models.users.user import User -from src.models.users.user_listening_history import UserListeningHistory -from src.queries import response_name_constants -from src.queries.query_helpers import ( - SortDirection, - SortMethod, - add_query_pagination, - add_users_to_tracks, - populate_track_metadata, -) -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -class GetUserListeningHistoryArgs(TypedDict): - # The current user logged in (from route param) - user_id: int - - # The maximum number of listens to return - limit: int - - # The offset for the listen history - offset: int - - # Optional filter for the returned results - query: Optional[str] - - # Optional sort method for the returned results - sort_method: Optional[SortMethod] - sort_direction: Optional[SortDirection] - - -def get_user_listening_history(args: GetUserListeningHistoryArgs): - """ - Returns a user's listening history. - - Args: - args: GetUserListeningHistoryArgs The parsed args from the request - - Returns: - Array of tracks the user listened to starting from most recently listened - """ - - db = get_db_read_replica() - with db.scoped_session() as session: - return _get_user_listening_history(session, args) - - -def _get_user_listening_history(session: Session, args: GetUserListeningHistoryArgs): - user_id = args["user_id"] - limit = args["limit"] - offset = args["offset"] - query = args["query"] - sort_method = args["sort_method"] - sort_direction = args["sort_direction"] - sort_fn = desc if sort_direction == SortDirection.desc else asc - - listening_history_results = ( - session.query(UserListeningHistory.listening_history).filter( - UserListeningHistory.user_id == user_id - ) - ).scalar() - - if not listening_history_results: - return [] - - # order listening history entries by their user's play counts so our track ids will be - # correct order when querying for track ids - if sort_method == SortMethod.most_listens_by_user: - listening_history_results = sort_listening_history_results_by_play_count_desc( - listening_history_results - ) - - # Map out all track ids and listen dates - track_ids = [] - listen_dates = {} - for listen in listening_history_results: - track_ids.append(listen["track_id"]) - listen_dates[listen["track_id"]] = listen["timestamp"] - - base_query = ( - session.query(TrackWithAggregates) - .filter(TrackWithAggregates.track_id.in_(track_ids)) - .filter(TrackWithAggregates.is_current == True) - .filter(TrackWithAggregates.is_delete == False) - .join(TrackWithAggregates.user) - .filter(User.is_deactivated == False) - ) - - if query is not None: - base_query = base_query.filter( - or_( - TrackWithAggregates.title.ilike(f"%{query.lower()}%"), - User.name.ilike(f"%{query.lower()}%"), - ) - ) - - base_query = sort_by_sort_method(sort_method, sort_fn, track_ids, base_query) - - # Add pagination - base_query = add_query_pagination(base_query, limit, offset) - query_results = base_query.all() - track_ids = track_ids[offset : offset + limit] - - tracks = helpers.query_result_to_list(query_results) - - # bundle peripheral info into track results - tracks = populate_track_metadata( - session, track_ids, tracks, current_user_id=user_id, track_has_aggregates=True - ) - tracks = add_users_to_tracks(session, tracks, current_user_id=user_id) - - for track in tracks: - track[response_name_constants.activity_timestamp] = listen_dates[ - track[response_name_constants.track_id] - ] - - return tracks - - -def sort_listening_history_results_by_play_count_desc(listening_history_results): - listening_histories_by_plays = [ - listening_history for listening_history in listening_history_results - ] - listening_histories_by_plays.sort( - key=lambda listen: listen.get("play_count", 1), reverse=True - ) - listening_history_results = listening_histories_by_plays - return listening_history_results - - -def sort_by_sort_method(sort_method, sort_fn, track_ids, base_query): - if sort_method == SortMethod.title: - return base_query.order_by(sort_fn(TrackWithAggregates.title)) - elif sort_method == SortMethod.artist_name: - return base_query.join(TrackWithAggregates.user, aliased=True).order_by( - sort_fn(User.name) - ) - elif sort_method == SortMethod.release_date: - return base_query.order_by( - sort_fn( - coalesce( - TrackWithAggregates.release_date, - TrackWithAggregates.created_at, - ) - ) - ) - elif sort_method == SortMethod.last_listen_date: - return base_query.order_by( - sort_fn(func.array_position(track_ids, TrackWithAggregates.track_id)) - ) - elif sort_method == SortMethod.plays: - return base_query.join(TrackWithAggregates.aggregate_play).order_by( - sort_fn(AggregatePlay.count) - ) - elif sort_method == SortMethod.reposts: - return base_query.join(TrackWithAggregates.aggregate_track).order_by( - sort_fn(AggregateTrack.repost_count) - ) - elif sort_method == SortMethod.saves: - return base_query.join(TrackWithAggregates.aggregate_track).order_by( - sort_fn(AggregateTrack.save_count) - ) - elif sort_method == SortMethod.most_listens_by_user: - return base_query.order_by( - (func.array_position(track_ids, TrackWithAggregates.track_id)) - ) - else: - return base_query.order_by( - sort_fn(func.array_position(track_ids, TrackWithAggregates.track_id)) - ) diff --git a/packages/discovery-provider/src/queries/get_user_playlist_update.py b/packages/discovery-provider/src/queries/get_user_playlist_update.py deleted file mode 100644 index e3e2b6096c2..00000000000 --- a/packages/discovery-provider/src/queries/get_user_playlist_update.py +++ /dev/null @@ -1,73 +0,0 @@ -import logging -from datetime import datetime -from typing import List, TypedDict - -from sqlalchemy.sql import text - -from src.utils import db_session - -logger = logging.getLogger(__name__) - - -sql = text( - """ -SELECT - p.playlist_id, - p.updated_at, - ps.seen_at -from - playlists p -INNER JOIN - saves s ON - s.save_item_id = p.playlist_id AND - s.is_current AND - NOT s.is_delete AND - s.save_type = 'playlist' AND - s.user_id = :user_id -LEFT JOIN - playlist_seen ps ON - ps.is_current AND - ps.playlist_id = p.playlist_id AND - ps.user_id = :user_id -where - p.is_current = true AND - p.is_delete = false AND - s.created_at < p.updated_at AND - (ps.seen_at is NULL OR p.updated_at > ps.seen_at) -order by p.playlist_id -""" -) - - -class PlaylistUpdate(TypedDict): - playlist_id: int - updated_at: datetime - last_seen_at: datetime - - -def get_user_playlist_update(user_id: int) -> List[PlaylistUpdate]: - """ - Returns list of playlist_ids for a user_id which have been updated - and the user has not seen - - Args: - user_id: int - - Returns: - List of playlist id - """ - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - rows = session.execute( - sql, - {"user_id": user_id}, - ) - playlists_with_updates: List[PlaylistUpdate] = [ - { - "playlist_id": row[0], - "updated_at": row[1], - "last_seen_at": row[2], - } - for row in rows - ] - return playlists_with_updates diff --git a/packages/discovery-provider/src/queries/get_user_signals.py b/packages/discovery-provider/src/queries/get_user_signals.py deleted file mode 100644 index 5cf50ed6d05..00000000000 --- a/packages/discovery-provider/src/queries/get_user_signals.py +++ /dev/null @@ -1,54 +0,0 @@ -from src.models.users.aggregate_user import AggregateUser -from src.models.users.user import User -from src.utils import db_session - - -def get_user_signals(handle): - db = db_session.get_db_read_replica() - with db.scoped_session() as session: - return _get_user_signals(session, handle) - - -def _get_user_signals(session, handle): - user_result = ( - session.query( - User.user_id, - User.profile_picture, - User.profile_picture_sizes, - User.cover_photo, - User.cover_photo_sizes, - User.wallet, - ) - .filter(User.handle == handle) - .filter(User.is_current == True) - .first() - ) - - if not user_result: - raise Exception(f"Could not find user id for handle '{handle}'") - - ( - user_id, - profile_picture, - profile_picture_sizes, - cover_photo, - cover_photo_sizes, - wallet, - ) = user_result - - user_follow_result = ( - session.query(AggregateUser.follower_count, AggregateUser.following_count) - .filter(AggregateUser.user_id == user_id) - .first() - ) - num_followers = user_follow_result[0] if user_follow_result else 0 - num_following = user_follow_result[1] if user_follow_result else 0 - - return { - "num_followers": num_followers, - "num_following": num_following, - "has_profile_picture": profile_picture is not None - or profile_picture_sizes is not None, - "has_cover_photo": cover_photo is not None or cover_photo_sizes is not None, - "wallet": wallet, - } diff --git a/packages/discovery-provider/src/queries/get_users_account.py b/packages/discovery-provider/src/queries/get_users_account.py deleted file mode 100644 index 4d7b9aadf9a..00000000000 --- a/packages/discovery-provider/src/queries/get_users_account.py +++ /dev/null @@ -1,159 +0,0 @@ -from typing import TypedDict - -from sqlalchemy import and_, asc, desc, or_ - -from src import exceptions -from src.models.playlists.playlist import Playlist -from src.models.social.save import Save, SaveType -from src.models.users.user import User -from src.queries.get_managed_users import is_active_manager -from src.queries.get_unpopulated_users import get_unpopulated_users -from src.queries.query_helpers import populate_user_metadata -from src.utils import helpers -from src.utils.db_session import get_db_read_replica - - -class GetAccountArgs(TypedDict): - wallet: str - authed_user_id: int - - -class GetAccountResponse(TypedDict): - user: dict - playlists: list[dict] - - -def _get_user_from_wallet(session, wallet: str): - # Create initial query - base_query = session.query(User) - # Don't return the user if they have no wallet or handle (user creation did not finish properly on chain) - base_query = base_query.filter(User.is_current == True, User.wallet != None) - - wallet = wallet.lower() - if len(wallet) == 42: - base_query = base_query.filter_by(wallet=wallet) - base_query = base_query.order_by( - desc(User.handle.isnot(None)), asc(User.created_at) - ) - else: - raise exceptions.ArgumentError("Invalid wallet length") - - # If user cannot be found, exit early and return empty response - user = base_query.first() - if not user: - return None - - return helpers.model_to_dictionary(user) - - -def _get_account_playlists(session, user_id: int): - # Get saved playlists / albums ids - saved_query = session.query(Save.save_item_id).filter( - Save.user_id == user_id, - Save.is_current == True, - Save.is_delete == False, - or_(Save.save_type == SaveType.playlist, Save.save_type == SaveType.album), - ) - - saved_query_results = saved_query.all() - save_collection_ids = [item[0] for item in saved_query_results] - - # Get Playlist/Albums saved or owned by the user - playlist_query = ( - session.query(Playlist) - .filter( - or_( - and_( - Playlist.is_current == True, - Playlist.is_delete == False, - Playlist.playlist_owner_id == user_id, - ), - and_( - Playlist.is_current == True, - Playlist.is_delete == False, - Playlist.playlist_id.in_(save_collection_ids), - ), - ) - ) - .order_by(desc(Playlist.created_at)) - ) - playlists = playlist_query.all() - playlists = helpers.query_result_to_list(playlists) - - playlist_owner_ids = list({playlist["playlist_owner_id"] for playlist in playlists}) - - # Get Users for the Playlist/Albums - users = get_unpopulated_users(session, playlist_owner_ids) - - user_map = {} - - stripped_playlists = [] - # Map the users to the playlists/albums - for playlist_owner in users: - user_map[playlist_owner["user_id"]] = playlist_owner - for playlist in playlists: - playlist_owner = user_map[playlist["playlist_owner_id"]] - stripped_playlist = { - "id": playlist["playlist_id"], - "name": playlist["playlist_name"], - "is_album": playlist["is_album"], - "permalink": playlist["permalink"], - "user": { - "id": playlist_owner["user_id"], - "handle": playlist_owner["handle"], - }, - } - if playlist_owner["is_deactivated"]: - stripped_playlist["user"]["is_deactivated"] = True - stripped_playlists.append(stripped_playlist) - return stripped_playlists - - -def get_account(args: GetAccountArgs) -> GetAccountResponse | None: - wallet = args.get("wallet") - authed_user_id = args.get("authed_user_id") - if not wallet: - raise exceptions.ArgumentError("Missing wallet param") - if not authed_user_id: - raise exceptions.ArgumentError("Missing authed_user_id param") - - db = get_db_read_replica() - with db.scoped_session() as session: - user = _get_user_from_wallet(session, args["wallet"]) - if not user: - return None - - user_id = user["user_id"] - - if user_id != authed_user_id and not is_active_manager(user_id, authed_user_id): - raise exceptions.PermissionError( - "You do not have permission to view this account" - ) - - # bundle peripheral info into user results - users = populate_user_metadata(session, [user_id], [user], user_id, True) - user = users[0] - playlists = _get_account_playlists(session, user_id) - return {"user": user, "playlists": playlists} - - -# DEPRECATED: Legacy query used by v0 endpoint -def get_users_account(args): - if "wallet" not in args: - raise exceptions.ArgumentError("Missing wallet param") - - db = get_db_read_replica() - with db.scoped_session() as session: - user = _get_user_from_wallet(session, args["wallet"]) - if not user: - return None - - user_id = user["user_id"] - - # bundle peripheral info into user results - users = populate_user_metadata(session, [user_id], [user], user_id, True) - user = users[0] - - user["playlists"] = _get_account_playlists(session, user_id) - - return user