def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")

    audius_db_url = os.getenv("audius_db_url")
    if audius_db_url:
        url = audius_db_url

    context.configure(url=url, target_metadata=target_metadata, literal_binds=True)

    with context.begin_transaction():
        context.run_migrations()

def run_migrations_offline ( ) : url = config . get_main_option ( "sqlalchemy.url" ) audius_db_url = os . getenv ( "audius_db_url" ) if audius_db_url : url = audius_db_url context . configure ( url = url , target_metadata = target_metadata , literal_binds = True ) with context . begin_transaction ( ) : context . run_migrations ( )


def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    audius_db_url = os.getenv("audius_db_url")

    if audius_db_url:
        config.set_main_option("sqlalchemy.url", audius_db_url)

    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(connection=connection, target_metadata=target_metadata)

        with context.begin_transaction():
            context.run_migrations()

def run_migrations_online ( ) : audius_db_url = os . getenv ( "audius_db_url" ) if audius_db_url : config . set_main_option ( "sqlalchemy.url" , audius_db_url ) connectable = engine_from_config ( config . get_section ( config . config_ini_section ) , prefix = "sqlalchemy." , poolclass = pool . NullPool , ) with connectable . connect ( ) as connection : context . configure ( connection = connection , target_metadata = target_metadata ) with context . begin_transaction ( ) : context . run_migrations ( )


Fixes segments from a string sorted %03d order to a proper integer based order .

def fix_segments(segments):
    """
    Fixes segments from a string sorted %03d order to a proper integer based order.

    Currently, we observe incorrect segment orders of
    099.ts
    100.ts
    1001.ts
    1002.ts
    ...
    101.ts

    This method takes in a list of segments and re orders them, returning the proper order.
    It does this by replicating a sort on the %03d naming schema and captures the indexes that move
    when that happens and then uses that relationship to back out what the original order should be.
    """
    fixed_segments = [None] * len(segments)
    # Produce tuples for the total length of segments (number, actual segment name), e.g. (0, segment000.ts)
    tuples = [(i, FMT % i) for i in range(0, len(segments))]
    # Sort segments by their stored file name fmt. This mirrors the error.
    sorted_tuples = sorted(tuples, key=lambda x: x[1])

    # Re-map out the tuples, capturing the actual index they should be at. This gives us
    # {0: 0, 1: 1, ..., 101: 1000, ..., 111: 101, ...}
    segment_map = {}
    for i in range(len(sorted_tuples)):
        proper_index = sorted_tuples[i][0]
        segment_map[i] = proper_index

    # Produce our final order and return
    for mapping in segment_map.items():
        fixed_segments[mapping[1]] = segments[mapping[0]]

    return fixed_segments


Un - fixes segments ( for down migration ) . Identical to fix_segments except proper_index and i are swapped to revert the change .

def unfix_segments(segments):
    """
    Un-fixes segments (for down migration).
    Identical to fix_segments, except proper_index and i are swapped to revert the change.
    """
    unfixed_segments = [None] * len(segments)
    tuples = [(i, FMT % i) for i in range(0, len(segments))]
    sorted_tuples = sorted(tuples, key=lambda x: x[1])
    segment_map = {}
    for i in range(len(sorted_tuples)):
        proper_index = sorted_tuples[i][0]
        # This line is the only difference from fix_segments.
        segment_map[proper_index] = i

    for mapping in segment_map.items():
        unfixed_segments[mapping[1]] = segments[mapping[0]]
    return unfixed_segments


Gets tracks . A note on caching strategy : - This method is cached at two layers : at the API via the @cache decorator and within this method using the shared get_unpopulated_tracks cache .

def get_tracks(args: GetTrackArgs):
    """
    Gets tracks.
    A note on caching strategy:
        - This method is cached at two layers: at the API via the @cache decorator,
        and within this method using the shared get_unpopulated_tracks cache.

        The shared cache only works when fetching via ID, so calls to fetch tracks
        via handle, asc/desc sort, or filtering by block_number won't hit the shared cache.
        These will hit the API cache unless they have a current_user_id included.

    """
    tracks = []

    db = get_db_read_replica()
    with db.scoped_session() as session:

        def get_tracks_and_ids():
            if "handle" in args:
                handle = args.get("handle")
                user = (
                    session.query(User.user_id)
                    .filter(User.handle_lc == handle.lower())
                    .first()
                )
                args["user_id"] = user.user_id

            if "routes" in args:
                # Convert the handles to user_ids
                routes = args.get("routes")
                handles = [route["handle"].lower() for route in routes]
                user_id_tuples = (
                    session.query(User.user_id, User.handle_lc)
                    .filter(User.handle_lc.in_(handles), User.is_current == True)
                    .all()
                )
                user_id_map = {handle: user_id for (user_id, handle) in user_id_tuples}
                args["routes"] = []
                for route in routes:
                    if route["handle"].lower() in user_id_map:
                        args["routes"].append(
                            {
                                "slug": route["slug"],
                                "owner_id": user_id_map[route["handle"].lower()],
                            }
                        )
                # If none of the handles were found, return empty lists
                if not args["routes"]:
                    return ([], [])

            can_use_shared_cache = (
                "id" in args
                and "min_block_number" not in args
                and "sort" not in args
                and "user_id" not in args
            )

            if can_use_shared_cache:
                should_filter_deleted = args.get("filter_deleted", False)
                tracks = get_unpopulated_tracks(
                    session, args["id"], should_filter_deleted
                )
                track_ids = list(map(lambda track: track["track_id"], tracks))
                return (tracks, track_ids)

            (limit, offset) = get_pagination_vars()
            args["limit"] = limit
            args["offset"] = offset

            tracks = _get_tracks(session, args)

            track_ids = list(map(lambda track: track["track_id"], tracks))

            return (tracks, track_ids)

        (tracks, track_ids) = get_tracks_and_ids()

        # bundle peripheral info into track results
        current_user_id = args.get("current_user_id")

        # remove track segments and download cids from deactivated user tracks and deleted tracks
        for track in tracks:
            if track["user"][0]["is_deactivated"] or track["is_delete"]:
                track["track_segments"] = []
                track["download"]["cid"] = None

        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


def search_user_tags(session, args):
    """
    Gets the users with tracks with a given tag

    Args:
        session: sqlalchemy db session instance
        args: dict of arguments
        args.search_str: string the tag search string
        args.current_user_id: id | null The user id making the query
        args.limit: number the query limit of number of returns tracks
        args.offset: number the query offset for results
        args.user_tag_count: number The number of tracks with the query tag

    Returns:
        list of users sorted by followee count
    """
    user_ids = (
        session.query(TagTrackUserMatview.owner_id)
        .filter(TagTrackUserMatview.tag == args["search_str"].lower())
        .group_by(TagTrackUserMatview.owner_id)
        .having(func.count(TagTrackUserMatview.owner_id) >= args["user_tag_count"])
        .all()
    )

    # user_ids is list of tuples - simplify to 1-D list
    user_ids = [i[0] for i in user_ids]

    users = (
        session.query(User)
        .filter(User.is_current == True, User.user_id.in_(user_ids))
        .all()
    )
    users = helpers.query_result_to_list(users)

    users = populate_user_metadata(session, user_ids, users, args["current_user_id"])

    followee_sorted_users = sorted(
        users, key=lambda i: i[response_name_constants.follower_count], reverse=True
    )

    followee_sorted_users = followee_sorted_users[
        slice(args["offset"], args["offset"] + args["limit"], 1)
    ]

    return followee_sorted_users


Returns the CID source ( e . g . CID is a metadata hash a cover photo a track segment etc . )

def get_cid_source(cid):
    """
    Returns the CID source (e.g. CID is a metadata hash, a cover photo, a track segment, etc.)

    Args: the observed CID
    """
    if cid is None:
        raise exceptions.ArgumentError("Input CID is invalid")

    have_lock = False
    update_lock = redis.lock("get_cid_source_lock", blocking_timeout=25)

    try:
        # Attempt to acquire lock - do not block if unable to acquire
        have_lock = update_lock.acquire(blocking=False)
        response = []
        if have_lock:
            db = db_session.get_db_read_replica()
            with db.scoped_session() as session:
                # Check to see if CID is of any type but a segment
                cid_source_res = sqlalchemy.text(
                    """
                    WITH cid_const AS (VALUES (:cid))
                    SELECT * FROM
                    (
                        (
                            SELECT
                                "user_id" as "id",
                                'users' as "table_name",
                                'metadata_multihash' as "type",
                                "is_current"
                            FROM "users" WHERE (table cid_const) = "metadata_multihash"
                        )
                        UNION ALL
                        (
                            SELECT
                                "user_id" as "id",
                                'users' as "table_name",
                                'profile_cover_images' as "type",
                                "is_current"
                            FROM
                                "users"
                            WHERE
                                (table cid_const) in (
                                    "profile_picture",
                                    "cover_photo",
                                    "profile_picture_sizes",
                                    "cover_photo_sizes"
                                )
                        )
                        UNION ALL
                        (
                                SELECT
                                "playlist_id" as "id",
                                'playlists' as "table_name",
                                'playlist_image_multihash' as "type",
                                "is_current"
                                FROM
                                    "playlists"
                                WHERE
                                    (table cid_const) in (
                                        "playlist_image_sizes_multihash",
                                        "playlist_image_multihash"
                                    )
                        )
                        UNION ALL
                        (
                            SELECT
                                "track_id" as "id",
                                'tracks' as "table_name",
                                'track_metadata' as "type",
                                "is_current"
                            FROM
                                "tracks"
                            WHERE
                                (table cid_const) = "metadata_multihash"
                        )
                        UNION ALL
                        (
                            SELECT
                                "track_id" as "id",
                                'tracks' as "table_name",
                                'cover_art_size' as "type",
                                "is_current"
                            FROM
                                "tracks"
                            WHERE
                                (table cid_const) = "cover_art_sizes"
                        )
                    ) as "outer"
                    """
                )
                cid_source = session.execute(cid_source_res, {"cid": cid}).fetchall()

                # If something is found, set response
                if len(cid_source) != 0:
                    response = [dict(row) for row in cid_source]

                # If CID was not found, check to see if it is a type segment
                if len(response) == 0:
                    cid_source_res = sqlalchemy.text(
                        """
                        WITH cid_const AS (VALUES (:cid))
                            SELECT
                                "track_id" as "id",
                                'tracks' as "table_name",
                                'segment' as "type",
                                "is_current"
                            FROM
                                (
                                    SELECT
                                        jb -> 'duration' as "d",
                                        jb -> 'multihash' :: varchar as "cid",
                                        "track_id",
                                        "is_current"
                                    FROM
                                        (
                                            SELECT
                                                jsonb_array_elements("track_segments") as "jb",
                                                "track_id",
                                                "is_current"
                                            FROM
                                                "tracks"
                                        ) as a
                                ) as a2
                            WHERE
                                "cid" ? (table cid_const)
                        """
                    )

                    cid_source = session.execute(
                        cid_source_res, {"cid": cid}
                    ).fetchall()

                    # If something is found, set response
                    if len(cid_source) != 0:
                        response = [dict(row) for row in cid_source]
        else:
            logger.warning("get_cid_source | Failed to acquire get_cid_source_lock")

        return response
    except Exception as e:
        logger.error("get_cid_source | Error with query: %s", exc_info=True)
        raise e
    finally:
        if have_lock:
            update_lock.release()


Sets the search similarity threshold to be used by % operator in queries . https : // www . postgresql . org / docs / 9 . 6 / pgtrgm . html

def set_search_similarity(cursor):
    """
    Sets the search similarity threshold to be used by % operator in queries.
    https://www.postgresql.org/docs/9.6/pgtrgm.html

    Note: set_limit was replaced by pg_trgm.similarity_threshold in PG 9.6.
    https://stackoverflow.com/a/11250001/11435157
    """
    try:
        cursor.execute(f"SET pg_trgm.similarity_threshold = {min_search_similarity}")
    except Exception as e:
        logger.error(f"Unable to set similarity_threshold: {e}")


Gets recommended tracks from trending by getting the currently cached tracks and then populating them .

def get_recommended_tracks(args, strategy):
    """Gets recommended tracks from trending by getting the currently cached tracks and then populating them."""
    exclusion_list = args.get("exclusion_list") or []
    time = args.get("time") if args.get("time") is not None else "week"
    current_user_id = args.get("user_id")
    args = {
        "time": time,
        "genre": args.get("genre", None),
        "with_users": True,
        "limit": args.get("limit"),
        "offset": 0,
    }

    # decode and add user_id if necessary
    if current_user_id:
        args["current_user_id"] = decode_string_id(current_user_id)

    tracks = get_trending_tracks(args, strategy)
    filtered_tracks = list(
        filter(lambda track: track["track_id"] not in exclusion_list, tracks)
    )

    random.shuffle(filtered_tracks)
    return list(map(extend_track, filtered_tracks))

def
get_recommended_tracks
(
args
,
strategy
)
:
exclusion_list
=
args
.
get
(
"exclusion_list"
)
or
[
]
time
=
args
.
get
(
"time"
)
if
args
.
get
(
"time"
)
is
not
else
"week"
current_user_id
=
args
.
get
(
"user_id"
)
args
=
{
"time"
:
time
,
"genre"
:
args
.
get
(
"genre"
,
)
,
"with_users"
:
True
,
"limit"
:
args
.
get
(
"limit"
)
,
"offset"
:
0
,
}
# decode and add user_id if necessary
if
current_user_id
:
args
[
"current_user_id"
]
=
decode_string_id
(
current_user_id
)
tracks
=
get_trending_tracks
(
args
,
strategy
)
filtered_tracks
=
list
(
filter
(
lambda
track
:
track
[
"track_id"
]
not
in
exclusion_list
,
tracks
)
)
random
.
shuffle
(
filtered_tracks
)
return
list
(
map
(
extend_track
,
filtered_tracks
)
)


Fetches the owner user id of the requested entity_type / entity_id

def get_owner_id(session, entity_type, entity_id):
    """
    Fetches the owner user id of the requested entity_type/entity_id

    Args:
        session: (obj) The start block number for querying for notifications
        entity_type: (string) Must be either 'track' | 'album' | 'playlist
        entity_id: (int) The id of the 'entity_type'

    Returns:
        owner_id: (int | None) The user id of the owner of the entity_type/entity_id
    """
    if entity_type == "track":
        owner_id_query = (
            session.query(Track.owner_id)
            .filter(
                Track.track_id == entity_id,
                Track.is_delete == False,
                Track.is_current == True,
            )
            .all()
        )
        if not owner_id_query:
            return None
        owner_id = owner_id_query[0][0]
        return owner_id

    if entity_type == "album":
        owner_id_query = (
            session.query(Playlist.playlist_owner_id)
            .filter(
                Playlist.playlist_id == entity_id,
                Playlist.is_delete == False,
                Playlist.is_current == True,
                Playlist.is_album == True,
            )
            .all()
        )
        if not owner_id_query:
            return None
        owner_id = owner_id_query[0][0]
        return owner_id

    if entity_type == "playlist":
        owner_id_query = (
            session.query(Playlist.playlist_owner_id)
            .filter(
                Playlist.playlist_id == entity_id,
                Playlist.is_delete == False,
                Playlist.is_current == True,
                Playlist.is_album == False,
            )
            .all()
        )
        if not owner_id_query:
            return None
        owner_id = owner_id_query[0][0]
        return owner_id

    return None

def get_owner_id ( session , entity_type , entity_id ) : if entity_type == "track" : owner_id_query = ( session . query ( Track . owner_id ) . filter ( Track . track_id == entity_id , Track . is_delete == False , Track . is_current == True , ) . all ( ) ) if not owner_id_query : return None owner_id = owner_id_query [ 0 ] [ 0 ] return owner_id if entity_type == "album" : owner_id_query = ( session . query ( Playlist . playlist_owner_id ) . filter ( Playlist . playlist_id == entity_id , Playlist . is_delete == False , Playlist . is_current == True , Playlist . is_album == True , ) . all ( ) ) if not owner_id_query : return None owner_id = owner_id_query [ 0 ] [ 0 ] return owner_id if entity_type == "playlist" : owner_id_query = ( session . query ( Playlist . playlist_owner_id ) . filter ( Playlist . playlist_id == entity_id , Playlist . is_delete == False , Playlist . is_current == True , Playlist . is_album == False , ) . all ( ) ) if not owner_id_query : return None owner_id = owner_id_query [ 0 ] [ 0 ] return owner_id return None


Get the notifications for remix tracks that are reposted / favorited by the parent remix author

def get_cosign_remix_notifications(session, max_block_number, remix_tracks):
    """
    Get the notifications for remix tracks that are reposted/favorited by the parent remix author

    Args:
        session: (DB)
        max_block_number: (int)
        remix_tracks: (Array<{ }>)
            'user_id'
            'item_id'
            const.notification_blocknumber
            const.notification_timestamp
            'item_owner_id'

    Returns:
        Array of cosign notifications

    """
    if not remix_tracks:
        return []

    remix_notifications = []
    remix_track_ids = [r["item_id"] for r in remix_tracks]

    # Query for all the parent tracks of the remix tracks
    tracks_subquery = (
        session.query(Track)
        .filter(
            Track.is_unlisted == False,
            Track.is_delete == False,
            Track.is_current == True,
        )
        .subquery()
    )

    parent_tracks = (
        session.query(
            Remix.child_track_id, Remix.parent_track_id, tracks_subquery.c.owner_id
        )
        .join(tracks_subquery, Remix.parent_track_id == tracks_subquery.c.track_id)
        .filter(Remix.child_track_id.in_(remix_track_ids))
        .all()
    )
    # Mapping of parent track users to child track to parent track
    parent_track_users_to_remixes = {}
    for track_parent in parent_tracks:
        [remix_track_id, remix_parent_id, remix_parent_user_id] = track_parent
        if remix_parent_user_id not in parent_track_users_to_remixes:
            parent_track_users_to_remixes[remix_parent_user_id] = {
                remix_track_id: remix_parent_id
            }
        else:
            parent_track_users_to_remixes[remix_parent_user_id][
                remix_track_id
            ] = remix_parent_id

    for remix_track in remix_tracks:
        user_id = remix_track["user_id"]
        track_id = remix_track["item_id"]

        if (
            user_id in parent_track_users_to_remixes
            and track_id in parent_track_users_to_remixes[user_id]
        ):
            remix_notifications.append(
                {
                    const.notification_type: const.notification_type_remix_cosign,
                    const.notification_blocknumber: remix_track[
                        const.notification_blocknumber
                    ],
                    const.notification_timestamp: remix_track[
                        const.notification_timestamp
                    ],
                    const.notification_initiator: user_id,
                    const.notification_metadata: {
                        const.notification_entity_id: track_id,
                        const.notification_entity_type: "track",
                        const.notification_entity_owner_id: remix_track[
                            "item_owner_id"
                        ],
                    },
                }
            )

    return remix_notifications


def notifications():
    """
    Fetches the notifications events that occurred between the given block numbers

    URL Params:
        min_block_number: (int) The start block number for querying for notifications
        max_block_number?: (int) The end block number for querying for notifications
        track_id?: (Array<int>) Array of track id for fetching the track's owner id
            and adding the track id to owner user id mapping to the `owners` response field
            NOTE: this is added for notification for listen counts

    Response - Json object w/ the following fields
        notifications: Array of notifications of shape:
            type: 'Follow' | 'Favorite' | 'Repost' | 'Create' | 'RemixCreate' | 'RemixCosign' | 'PlaylistUpdate'
            blocknumber: (int) blocknumber of notification
            timestamp: (string) timestamp of notification
            initiator: (int) the user id that caused this notification
            metadata?: (any) additional information about the notification
                entity_id?: (int) the id of the target entity (ie. playlist id of a playlist that is reposted)
                entity_type?: (string) the type of the target entity
                entity_owner_id?: (int) the id of the target entity's owner (if applicable)
                playlist_update_timestamp?: (string) timestamp of last update of a given playlist
                playlist_update_users?: (array<int>) user ids which favorited a given playlist

        info: Dictionary of metadata w/ min_block_number & max_block_number fields

        milestones: Dictionary mapping of follows/reposts/favorites (processed within the blocks params)
            Root fields:
                follower_counts: Contains a dictionary of user id => follower count (up to the max_block_number)
                repost_counts: Contains a dictionary tracks/albums/playlists of id to repost count
                favorite_counts: Contains a dictionary tracks/albums/playlists of id to favorite count

        owners: Dictionary containing the mapping for track id / playlist id / album -> owner user id
            The root keys are 'tracks', 'playlists', 'albums' and each contains the id to owner id mapping
    """

    db = get_db_read_replica()
    min_block_number = request.args.get("min_block_number", type=int)
    max_block_number = request.args.get("max_block_number", type=int)

    track_ids_to_owner = []
    try:
        track_ids_str_list = request.args.getlist("track_id")
        track_ids_to_owner = [int(y) for y in track_ids_str_list]
    except Exception as e:
        logger.error(f"Failed to retrieve track list {e}")

    # Max block number is not explicitly required (yet)
    if not min_block_number and min_block_number != 0:
        return api_helpers.error_response({"msg": "Missing min block number"}, 400)

    if not max_block_number:
        max_block_number = min_block_number + max_block_diff
    elif (max_block_number - min_block_number) > max_block_diff:
        max_block_number = min_block_number + max_block_diff

    with db.scoped_session() as session:
        current_block_query = session.query(Block).filter_by(is_current=True)
        current_block_query_results = current_block_query.all()
        current_block = current_block_query_results[0]
        current_max_block_num = current_block.number
        if current_max_block_num < max_block_number:
            max_block_number = current_max_block_num

    notification_metadata = {
        "min_block_number": min_block_number,
        "max_block_number": max_block_number,
    }

    # Retrieve milestones statistics
    milestone_info = {}

    # Cache owner info for network entities and pass in w/results
    owner_info = {const.tracks: {}, const.albums: {}, const.playlists: {}}

    start_time = datetime.now()
    logger.info(f"notifications.py | start_time ${start_time}")

    # List of notifications generated from current protocol state
    notifications_unsorted = []
    with db.scoped_session() as session:
        #
        # Query relevant follow information
        #
        follow_query = session.query(Follow)

        # Impose min block number restriction
        follow_query = follow_query.filter(
            Follow.is_current == True,
            Follow.is_delete == False,
            Follow.blocknumber > min_block_number,
            Follow.blocknumber <= max_block_number,
        )

        follow_results = follow_query.all()
        # Used to retrieve follower counts for this window
        followed_users = []
        # Represents all follow notifications
        follow_notifications = []
        for entry in follow_results:
            follow_notif = {
                const.notification_type: const.notification_type_follow,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.follower_user_id,
                const.notification_metadata: {
                    const.notification_follower_id: entry.follower_user_id,
                    const.notification_followee_id: entry.followee_user_id,
                },
            }
            follow_notifications.append(follow_notif)
            # Add every user who gained a new follower
            followed_users.append(entry.followee_user_id)

        # Query count for any user w/new followers
        follower_counts = get_follower_count_dict(
            session, followed_users, max_block_number
        )
        milestone_info["follower_counts"] = follower_counts

        notifications_unsorted.extend(follow_notifications)

        logger.info(f"notifications.py | followers at {datetime.now() - start_time}")

        #
        # Query relevant favorite information
        #
        favorites_query = session.query(Save)
        favorites_query = favorites_query.filter(
            Save.is_current == True,
            Save.is_delete == False,
            Save.blocknumber > min_block_number,
            Save.blocknumber <= max_block_number,
        )
        favorite_results = favorites_query.all()

        # ID lists to query count aggregates
        favorited_track_ids = []
        favorited_album_ids = []
        favorited_playlist_ids = []

        # List of favorite notifications
        favorite_notifications = []
        favorite_remix_tracks = []

        for entry in favorite_results:
            favorite_notif = {
                const.notification_type: const.notification_type_favorite,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.user_id,
            }
            save_type = entry.save_type
            save_item_id = entry.save_item_id
            metadata = {
                const.notification_entity_type: save_type,
                const.notification_entity_id: save_item_id,
            }

            # NOTE if deleted, the favorite can still exist
            # TODO: Can we aggregate all owner queries and perform at once...?
            if save_type == SaveType.track:
                owner_id = get_owner_id(session, "track", save_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                favorited_track_ids.append(save_item_id)
                owner_info[const.tracks][save_item_id] = owner_id

                favorite_remix_tracks.append(
                    {
                        const.notification_blocknumber: entry.blocknumber,
                        const.notification_timestamp: entry.created_at,
                        "user_id": entry.user_id,
                        "item_owner_id": owner_id,
                        "item_id": save_item_id,
                    }
                )

            elif save_type == SaveType.album:
                owner_id = get_owner_id(session, "album", save_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                favorited_album_ids.append(save_item_id)
                owner_info[const.albums][save_item_id] = owner_id

            elif save_type == SaveType.playlist:
                owner_id = get_owner_id(session, "playlist", save_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                favorited_playlist_ids.append(save_item_id)
                owner_info[const.playlists][save_item_id] = owner_id

            favorite_notif[const.notification_metadata] = metadata
            favorite_notifications.append(favorite_notif)
        notifications_unsorted.extend(favorite_notifications)

        track_favorite_dict = {}
        album_favorite_dict = {}
        playlist_favorite_dict = {}

        if favorited_track_ids:
            track_favorite_counts = get_save_counts(
                session,
                False,
                False,
                favorited_track_ids,
                [SaveType.track],
                max_block_number,
            )
            track_favorite_dict = dict(track_favorite_counts)

            favorite_remix_notifications = get_cosign_remix_notifications(
                session, max_block_number, favorite_remix_tracks
            )
            notifications_unsorted.extend(favorite_remix_notifications)

        if favorited_album_ids:
            album_favorite_counts = get_save_counts(
                session,
                False,
                False,
                favorited_album_ids,
                [SaveType.album],
                max_block_number,
            )
            album_favorite_dict = dict(album_favorite_counts)

        if favorited_playlist_ids:
            playlist_favorite_counts = get_save_counts(
                session,
                False,
                False,
                favorited_playlist_ids,
                [SaveType.playlist],
                max_block_number,
            )
            playlist_favorite_dict = dict(playlist_favorite_counts)

        milestone_info[const.notification_favorite_counts] = {}
        milestone_info[const.notification_favorite_counts][
            const.tracks
        ] = track_favorite_dict
        milestone_info[const.notification_favorite_counts][
            const.albums
        ] = album_favorite_dict
        milestone_info[const.notification_favorite_counts][
            const.playlists
        ] = playlist_favorite_dict

        logger.info(f"notifications.py | favorites at {datetime.now() - start_time}")

        #
        # Query relevant tier change information
        #
        balance_change_query = session.query(UserBalanceChange)

        # Impose min block number restriction
        balance_change_query = balance_change_query.filter(
            UserBalanceChange.blocknumber > min_block_number,
            UserBalanceChange.blocknumber <= max_block_number,
        )

        balance_change_results = balance_change_query.all()
        tier_change_notifications = []

        for entry in balance_change_results:
            prev = int(entry.previous_balance)
            current = int(entry.current_balance)
            # Check for a tier change and add to tier_change_notification
            tier = None
            if prev < 100000 <= current:
                tier = "platinum"
            elif prev < 10000 <= current:
                tier = "gold"
            elif prev < 100 <= current:
                tier = "silver"
            elif prev < 10 <= current:
                tier = "bronze"

            if tier is not None:
                tier_change_notif = {
                    const.notification_type: const.notification_type_tier_change,
                    const.notification_blocknumber: entry.blocknumber,
                    const.notification_timestamp: datetime.now(),
                    const.notification_initiator: entry.user_id,
                    const.notification_metadata: {
                        const.notification_tier: tier,
                    },
                }
                tier_change_notifications.append(tier_change_notif)

        notifications_unsorted.extend(tier_change_notifications)

        logger.info(
            f"notifications.py | balance change at {datetime.now() - start_time}"
        )

        #
        # Query relevant repost information
        #
        repost_query = session.query(Repost)
        repost_query = repost_query.filter(
            Repost.is_current == True,
            Repost.is_delete == False,
            Repost.blocknumber > min_block_number,
            Repost.blocknumber <= max_block_number,
        )
        repost_results = repost_query.all()

        # ID lists to query counts
        reposted_track_ids = []
        reposted_album_ids = []
        reposted_playlist_ids = []

        # List of repost notifications
        repost_notifications = []

        # List of repost notifications
        repost_remix_notifications = []
        repost_remix_tracks = []

        for entry in repost_results:
            repost_notif = {
                const.notification_type: const.notification_type_repost,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.user_id,
            }
            repost_type = entry.repost_type
            repost_item_id = entry.repost_item_id
            metadata = {
                const.notification_entity_type: repost_type,
                const.notification_entity_id: repost_item_id,
            }
            if repost_type == RepostType.track:
                owner_id = get_owner_id(session, "track", repost_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                reposted_track_ids.append(repost_item_id)
                owner_info[const.tracks][repost_item_id] = owner_id
                repost_remix_tracks.append(
                    {
                        const.notification_blocknumber: entry.blocknumber,
                        const.notification_timestamp: entry.created_at,
                        "user_id": entry.user_id,
                        "item_owner_id": owner_id,
                        "item_id": repost_item_id,
                    }
                )

            elif repost_type == RepostType.album:
                owner_id = get_owner_id(session, "album", repost_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                reposted_album_ids.append(repost_item_id)
                owner_info[const.albums][repost_item_id] = owner_id

            elif repost_type == RepostType.playlist:
                owner_id = get_owner_id(session, "playlist", repost_item_id)
                if not owner_id:
                    continue
                metadata[const.notification_entity_owner_id] = owner_id
                reposted_playlist_ids.append(repost_item_id)
                owner_info[const.playlists][repost_item_id] = owner_id

            repost_notif[const.notification_metadata] = metadata
            repost_notifications.append(repost_notif)

        # Append repost notifications
        notifications_unsorted.extend(repost_notifications)

        track_repost_count_dict = {}
        album_repost_count_dict = {}
        playlist_repost_count_dict = {}

        # Aggregate repost counts for relevant fields
        # Used to notify users of entity-specific milestones
        if reposted_track_ids:
            track_repost_counts = get_repost_counts(
                session,
                False,
                False,
                reposted_track_ids,
                [RepostType.track],
                max_block_number,
            )
            track_repost_count_dict = dict(track_repost_counts)

            repost_remix_notifications = get_cosign_remix_notifications(
                session, max_block_number, repost_remix_tracks
            )
            notifications_unsorted.extend(repost_remix_notifications)

        if reposted_album_ids:
            album_repost_counts = get_repost_counts(
                session,
                False,
                False,
                reposted_album_ids,
                [RepostType.album],
                max_block_number,
            )
            album_repost_count_dict = dict(album_repost_counts)

        if reposted_playlist_ids:
            playlist_repost_counts = get_repost_counts(
                session,
                False,
                False,
                reposted_playlist_ids,
                [RepostType.playlist],
                max_block_number,
            )
            playlist_repost_count_dict = dict(playlist_repost_counts)

        milestone_info[const.notification_repost_counts] = {}
        milestone_info[const.notification_repost_counts][
            const.tracks
        ] = track_repost_count_dict
        milestone_info[const.notification_repost_counts][
            const.albums
        ] = album_repost_count_dict
        milestone_info[const.notification_repost_counts][
            const.playlists
        ] = playlist_repost_count_dict

        # Query relevant created entity notification - tracks/albums/playlists
        created_notifications = []

        logger.info(f"notifications.py | reposts at {datetime.now() - start_time}")

        #
        # Query relevant created tracks for remix information
        #
        remix_created_notifications = []

        # Aggregate track notifs
        tracks_query = session.query(Track)
        # TODO: Is it valid to use Track.is_current here? Might not be the right info...
        tracks_query = tracks_query.filter(
            Track.is_unlisted == False,
            Track.is_delete == False,
            Track.stem_of == None,
            Track.blocknumber > min_block_number,
            Track.blocknumber <= max_block_number,
        )
        tracks_query = tracks_query.filter(Track.created_at == Track.updated_at)
        track_results = tracks_query.all()
        for entry in track_results:
            track_notif = {
                const.notification_type: const.notification_type_create,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.owner_id,
                # TODO: is entity owner id necessary for tracks?
                const.notification_metadata: {
                    const.notification_entity_type: "track",
                    const.notification_entity_id: entry.track_id,
                    const.notification_entity_owner_id: entry.owner_id,
                },
            }
            created_notifications.append(track_notif)

            if entry.remix_of:
                # Add notification to remix track owner
                parent_remix_tracks = [
                    t["parent_track_id"] for t in entry.remix_of["tracks"]
                ]
                remix_track_parents = (
                    session.query(Track.owner_id, Track.track_id)
                    .filter(
                        Track.track_id.in_(parent_remix_tracks),
                        Track.is_unlisted == False,
                        Track.is_delete == False,
                        Track.is_current == True,
                    )
                    .all()
                )
                for remix_track_parent in remix_track_parents:
                    [
                        remix_track_parent_owner,
                        remix_track_parent_id,
                    ] = remix_track_parent
                    remix_notif = {
                        const.notification_type: const.notification_type_remix_create,
                        const.notification_blocknumber: entry.blocknumber,
                        const.notification_timestamp: entry.created_at,
                        const.notification_initiator: entry.owner_id,
                        # TODO: is entity owner id necessary for tracks?
                        const.notification_metadata: {
                            const.notification_entity_type: "track",
                            const.notification_entity_id: entry.track_id,
                            const.notification_entity_owner_id: entry.owner_id,
                            const.notification_remix_parent_track_user_id: remix_track_parent_owner,
                            const.notification_remix_parent_track_id: remix_track_parent_id,
                        },
                    }
                    remix_created_notifications.append(remix_notif)

        logger.info(f"notifications.py | remixes at {datetime.now() - start_time}")

        # Handle track update notifications
        # TODO: Consider switching blocknumber for updated at?
        updated_tracks_query = session.query(Track)
        updated_tracks_query = updated_tracks_query.filter(
            Track.is_unlisted == False,
            Track.stem_of == None,
            Track.created_at != Track.updated_at,
            Track.blocknumber > min_block_number,
            Track.blocknumber <= max_block_number,
        )
        updated_tracks = updated_tracks_query.all()

        prev_tracks = get_prev_track_entries(updated_tracks)

        for prev_entry in prev_tracks:
            entry = next(t for t in updated_tracks if t.track_id == prev_entry.track_id)
            logger.info(
                f"notifications.py | single track update {entry.track_id} {entry.blocknumber} {datetime.now() - start_time}"
            )

            # Tracks that were unlisted and turned to public
            if prev_entry.is_unlisted == True:
                logger.info(
                    f"notifications.py | single track update to public {datetime.now() - start_time}"
                )
                track_notif = {
                    const.notification_type: const.notification_type_create,
                    const.notification_blocknumber: entry.blocknumber,
                    const.notification_timestamp: entry.created_at,
                    const.notification_initiator: entry.owner_id,
                    # TODO: is entity owner id necessary for tracks?
                    const.notification_metadata: {
                        const.notification_entity_type: "track",
                        const.notification_entity_id: entry.track_id,
                        const.notification_entity_owner_id: entry.owner_id,
                    },
                }
                created_notifications.append(track_notif)

            # Tracks that were not remixes and turned into remixes
            if not prev_entry.remix_of and entry.remix_of:
                # Add notification to remix track owner
                parent_remix_tracks = [
                    t["parent_track_id"] for t in entry.remix_of["tracks"]
                ]
                remix_track_parents = (
                    session.query(Track.owner_id, Track.track_id)
                    .filter(
                        Track.track_id.in_(parent_remix_tracks),
                        Track.is_unlisted == False,
                        Track.is_delete == False,
                        Track.is_current == True,
                    )
                    .all()
                )
                logger.info(
                    f"notifications.py | single track update parents {remix_track_parents} {datetime.now() - start_time}"
                )
                for remix_track_parent in remix_track_parents:
                    [
                        remix_track_parent_owner,
                        remix_track_parent_id,
                    ] = remix_track_parent
                    remix_notif = {
                        const.notification_type: const.notification_type_remix_create,
                        const.notification_blocknumber: entry.blocknumber,
                        const.notification_timestamp: entry.created_at,
                        const.notification_initiator: entry.owner_id,
                        # TODO: is entity owner id necessary for tracks?
                        const.notification_metadata: {
                            const.notification_entity_type: "track",
                            const.notification_entity_id: entry.track_id,
                            const.notification_entity_owner_id: entry.owner_id,
                            const.notification_remix_parent_track_user_id: remix_track_parent_owner,
                            const.notification_remix_parent_track_id: remix_track_parent_id,
                        },
                    }
                    remix_created_notifications.append(remix_notif)

        notifications_unsorted.extend(remix_created_notifications)

        logger.info(
            f"notifications.py | track updates at {datetime.now() - start_time}"
        )

        # Aggregate playlist/album notifs
        collection_query = session.query(Playlist)
        # TODO: Is it valid to use is_current here? Might not be the right info...
        collection_query = collection_query.filter(
            Playlist.is_delete == False,
            Playlist.is_private == False,
            Playlist.blocknumber > min_block_number,
            Playlist.blocknumber <= max_block_number,
        )
        collection_query = collection_query.filter(
            Playlist.created_at == Playlist.updated_at
        )
        collection_results = collection_query.all()

        for entry in collection_results:
            collection_notif = {
                const.notification_type: const.notification_type_create,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.playlist_owner_id,
            }
            metadata = {
                const.notification_entity_id: entry.playlist_id,
                const.notification_entity_owner_id: entry.playlist_owner_id,
                const.notification_collection_content: entry.playlist_contents,
            }

            if entry.is_album:
                metadata[const.notification_entity_type] = "album"
            else:
                metadata[const.notification_entity_type] = "playlist"
            collection_notif[const.notification_metadata] = metadata
            created_notifications.append(collection_notif)

        # Playlists that were private and turned to public aka 'published'
        # TODO: Consider switching blocknumber for updated at?
        publish_playlists_query = session.query(Playlist)
        publish_playlists_query = publish_playlists_query.filter(
            Playlist.is_private == False,
            Playlist.created_at != Playlist.updated_at,
            Playlist.blocknumber > min_block_number,
            Playlist.blocknumber <= max_block_number,
        )
        publish_playlist_results = publish_playlists_query.all()
        for entry in publish_playlist_results:
            prev_entry_query = (
                session.query(Playlist)
                .filter(
                    Playlist.playlist_id == entry.playlist_id,
                    Playlist.blocknumber < entry.blocknumber,
                )
                .order_by(desc(Playlist.blocknumber))
            )
            # Previous private entry indicates transition to public, triggering a notification
            prev_entry = prev_entry_query.first()
            if prev_entry.is_private == True:
                publish_playlist_notif = {
                    const.notification_type: const.notification_type_create,
                    const.notification_blocknumber: entry.blocknumber,
                    const.notification_timestamp: entry.created_at,
                    const.notification_initiator: entry.playlist_owner_id,
                }
                metadata = {
                    const.notification_entity_id: entry.playlist_id,
                    const.notification_entity_owner_id: entry.playlist_owner_id,
                    const.notification_collection_content: entry.playlist_contents,
                    const.notification_entity_type: "playlist",
                }
                publish_playlist_notif[const.notification_metadata] = metadata
                created_notifications.append(publish_playlist_notif)

        notifications_unsorted.extend(created_notifications)

        logger.info(f"notifications.py | playlists at {datetime.now() - start_time}")

        # Get additional owner info as requested for listen counts
        tracks_owner_query = session.query(Track).filter(
            Track.is_current == True, Track.track_id.in_(track_ids_to_owner)
        )
        track_owner_results = tracks_owner_query.all()
        for entry in track_owner_results:
            owner = entry.owner_id
            track_id = entry.track_id
            owner_info[const.tracks][track_id] = owner

        # Get playlist updates
        today = date.today()
        thirty_days_ago = today - timedelta(days=30)
        thirty_days_ago_time = datetime(
            thirty_days_ago.year, thirty_days_ago.month, thirty_days_ago.day, 0, 0, 0
        )
        playlist_update_query = session.query(Playlist)
        playlist_update_query = playlist_update_query.filter(
            Playlist.is_current == True,
            Playlist.is_delete == False,
            Playlist.last_added_to >= thirty_days_ago_time,
            Playlist.blocknumber > min_block_number,
            Playlist.blocknumber <= max_block_number,
        )

        playlist_update_results = playlist_update_query.all()

        # Represents all playlist update notifications
        playlist_update_notifications = []
        playlist_update_notifs_by_playlist_id = {}
        for entry in playlist_update_results:
            playlist_update_notifs_by_playlist_id[entry.playlist_id] = {
                const.notification_type: const.notification_type_playlist_update,
                const.notification_blocknumber: entry.blocknumber,
                const.notification_timestamp: entry.created_at,
                const.notification_initiator: entry.playlist_owner_id,
                const.notification_metadata: {
                    const.notification_entity_id: entry.playlist_id,
                    const.notification_entity_type: "playlist",
                    const.notification_playlist_update_timestamp: entry.last_added_to,
                },
            }

        # get all favorited playlists
        # playlists may have been favorited outside the blocknumber bounds
        # e.g. before the min_block_number
        playlist_favorites_query = session.query(Save)
        playlist_favorites_query = playlist_favorites_query.filter(
            Save.is_current == True,
            Save.is_delete == False,
            Save.save_type == SaveType.playlist,
            Save.save_item_id.in_(playlist_update_notifs_by_playlist_id.keys()),
        )
        playlist_favorites_results = playlist_favorites_query.all()

        # dictionary of playlist id => users that favorited said playlist
        # e.g. { playlist1: [user1, user2, ...], ... }
        # we need this dictionary to know which users need to be notified of a playlist update
        users_that_favorited_playlists_dict = ft.reduce(
            lambda accumulator, current: accumulator.update(
                {
                    current.save_item_id: accumulator[current.save_item_id]
                    + [current.user_id]
                    if current.save_item_id in accumulator
                    else [current.user_id]
                }
            )
            or accumulator,
            playlist_favorites_results,
            {},
        )

        for playlist_id in users_that_favorited_playlists_dict:
            # TODO: We probably do not need this check because we are filtering
            # playlist_favorites_query to only matching ids
            if playlist_id not in playlist_update_notifs_by_playlist_id:
                continue
            playlist_update_notif = playlist_update_notifs_by_playlist_id[playlist_id]
            playlist_update_notif[const.notification_metadata].update(
                {
                    const.notification_playlist_update_users: users_that_favorited_playlists_dict[
                        playlist_id
                    ]
                }
            )
            playlist_update_notifications.append(playlist_update_notif)

        notifications_unsorted.extend(playlist_update_notifications)

        logger.info(
            f"notifications.py | playlist updates at {datetime.now() - start_time}"
        )

    # Final sort - TODO: can we sort by timestamp?
    sorted_notifications = sorted(
        notifications_unsorted,
        key=lambda i: i[const.notification_blocknumber],
        reverse=False,
    )

    return api_helpers.success_response(
        {
            "notifications": sorted_notifications,
            "info": notification_metadata,
            "milestones": milestone_info,
            "owners": owner_info,
        }
    )


def solana_notifications():
    """
    Fetches the notifications events that occurred between the given slot numbers

    URL Params:
        min_slot_number: (int) The start slot number for querying for notifications
        max_slot_number?: (int) The end slot number for querying for notifications

    Response - Json object w/ the following fields
        notifications: Array of notifications of shape:
            type: 'ChallengeReward' | 'MilestoneListen'
            slot: (int) slot number of notification
            initiator: (int) the user id that caused this notification
            metadata?: (any) additional information about the notification
                challenge_id?: (int) completed challenge id for challenge reward notifications

        info: Dictionary of metadata w/ min_slot_number & max_slot_number fields
    """
    db = get_db_read_replica()
    redis = get_redis()
    min_slot_number = request.args.get("min_slot_number", type=int)
    max_slot_number = request.args.get("max_slot_number", type=int)

    # Max slot number is not explicitly required (yet)
    if not min_slot_number and min_slot_number != 0:
        return api_helpers.error_response({"msg": "Missing min slot number"}, 400)

    if not max_slot_number or (max_slot_number - min_slot_number) > max_slot_diff:
        max_slot_number = min_slot_number + max_slot_diff

    max_valid_slot = get_max_slot(redis)
    max_slot_number = min(max_slot_number, max_valid_slot)

    notifications_unsorted = []
    notification_metadata = {
        "min_slot_number": min_slot_number,
        "max_slot_number": max_slot_number,
    }

    with db.scoped_session() as session:
        #
        # Query relevant challenge disbursement information for challenge reward notifications
        #
        challenge_disbursement_results = (
            session.query(ChallengeDisbursement)
            .filter(
                ChallengeDisbursement.slot >= min_slot_number,
                ChallengeDisbursement.slot <= max_slot_number,
            )
            .all()
        )

        challenge_reward_notifications = []
        for result in challenge_disbursement_results:
            challenge_reward_notifications.append(
                {
                    const.solana_notification_type: const.solana_notification_type_challenge_reward,
                    const.solana_notification_slot: result.slot,
                    const.solana_notification_initiator: result.user_id,
                    const.solana_notification_metadata: {
                        const.solana_notification_challenge_id: result.challenge_id,
                    },
                }
            )

        track_listen_milestone: List[Tuple(Milestone, int)] = (
            session.query(Milestone, Track.owner_id)
            .filter(
                Milestone.name == LISTEN_COUNT_MILESTONE,
                Milestone.slot >= min_slot_number,
                Milestone.slot <= max_slot_number,
            )
            .join(Track, Track.track_id == Milestone.id and Track.is_current == True)
            .all()
        )

        track_listen_milestones = []
        for result in track_listen_milestone:
            track_milestone, track_owner_id = result
            track_listen_milestones.append(
                {
                    const.solana_notification_type: const.solana_notification_type_listen_milestone,
                    const.solana_notification_slot: track_milestone.slot,
                    const.solana_notification_initiator: track_owner_id,  # owner_id
                    const.solana_notification_metadata: {
                        const.solana_notification_threshold: track_milestone.threshold,
                        const.notification_entity_id: track_milestone.id,  # track_id
                        const.notification_entity_type: "track",
                    },
                }
            )

        notifications_unsorted.extend(challenge_reward_notifications)
        notifications_unsorted.extend(track_listen_milestones)

    # Final sort
    sorted_notifications = sorted(
        notifications_unsorted,
        key=lambda i: i[const.solana_notification_slot],
        reverse=False,
    )

    return api_helpers.success_response(
        {
            "notifications": sorted_notifications,
            "info": notification_metadata,
        }
    )


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 ( args : GetUserListeningHistoryArgs ) : db = get_db_read_replica ( ) with db . scoped_session ( ) as session : return _get_user_listening_history ( session , args )


Gets the repost feed for a user ( e . g . stalking a user )

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 ( user_id : int , args : GetRepostFeedForUserArgs ) : db = get_db_read_replica ( ) with db . scoped_session ( ) as session : return _get_repost_feed_for_user ( session , user_id , args )


Returns a map : { track_id : string created_at : string owner_id : number windowed_save_count : number save_count : number repost_count : number windowed_repost_count : number owner_follower_count : number karma : number listens : number owner_verified : boolean }

def get_scorable_track_data(session, redis_instance, strategy):
    """
    Returns a map: {
        "track_id": string
        "created_at": string
        "owner_id": number
        "windowed_save_count": number
        "save_count": number
        "repost_count": number
        "windowed_repost_count": number
        "owner_follower_count": number
        "karma": number
        "listens": number
        "owner_verified": boolean
    }
    """

    score_params = strategy.get_score_params()
    S = score_params["S"]
    r = score_params["r"]
    q = score_params["q"]
    o = score_params["o"]
    f = score_params["f"]
    qr = score_params["qr"]
    xf = score_params["xf"]
    pt = score_params["pt"]
    trending_key = make_trending_cache_key("week", None, strategy.version)
    track_ids = []
    old_trending = get_pickled_key(redis_instance, trending_key)
    if old_trending:
        track_ids = old_trending[1]
    exclude_track_ids = track_ids[:qr]

    # Get followers
    follower_query = (
        session.query(
            Follow.followee_user_id.label("user_id"),
            User.is_verified.label("is_verified"),
            func.count(Follow.followee_user_id).label("follower_count"),
        )
        .join(User, User.user_id == Follow.followee_user_id)
        .filter(
            Follow.is_current == True,
            Follow.is_delete == False,
            User.is_current == True,
            Follow.created_at < (datetime.now() - timedelta(days=f)),
        )
        .group_by(Follow.followee_user_id, User.is_verified)
    ).subquery()

    base_query = (
        session.query(
            AggregatePlays.play_item_id.label("track_id"),
            follower_query.c.user_id,
            follower_query.c.follower_count,
            AggregatePlays.count,
            Track.created_at,
            follower_query.c.is_verified,
        )
        .join(Track, Track.track_id == AggregatePlays.play_item_id)
        .join(follower_query, follower_query.c.user_id == Track.owner_id)
        .join(AggregateUser, AggregateUser.user_id == Track.owner_id)
        .filter(
            Track.is_current == True,
            Track.is_delete == False,
            Track.is_unlisted == False,
            Track.stem_of == None,
            Track.track_id.notin_(exclude_track_ids),
            Track.created_at >= (datetime.now() - timedelta(days=o)),
            follower_query.c.follower_count < S,
            follower_query.c.follower_count >= pt,
            AggregateUser.following_count < r,
            AggregatePlays.count >= q,
        )
    ).all()

    tracks_map = {
        record[0]: {
            "track_id": record[0],
            "created_at": record[4].isoformat(timespec="seconds"),
            "owner_id": record[1],
            "windowed_save_count": 0,
            "save_count": 0,
            "repost_count": 0,
            "windowed_repost_count": 0,
            "owner_follower_count": record[2],
            "karma": 1,
            "listens": record[3],
            "owner_verified": record[5],
        }
        for record in base_query
    }

    track_ids = [record[0] for record in base_query]

    # Get all the extra values
    repost_counts = get_repost_counts(
        session, False, False, track_ids, [RepostType.track]
    )

    windowed_repost_counts = get_repost_counts(
        session, False, False, track_ids, [RepostType.track], None, "week"
    )

    save_counts = get_save_counts(session, False, False, track_ids, [SaveType.track])

    windowed_save_counts = get_save_counts(
        session, False, False, track_ids, [SaveType.track], None, "week"
    )

    karma_scores = get_karma(session, tuple(track_ids), strategy, None, False, xf)

    # Associate all the extra data
    for (track_id, repost_count) in repost_counts:
        tracks_map[track_id]["repost_count"] = repost_count
    for (track_id, repost_count) in windowed_repost_counts:
        tracks_map[track_id]["windowed_repost_count"] = repost_count
    for (track_id, save_count) in save_counts:
        tracks_map[track_id]["save_count"] = save_count
    for (track_id, save_count) in windowed_save_counts:
        tracks_map[track_id]["windowed_save_count"] = save_count
    for (track_id, karma) in karma_scores:
        tracks_map[track_id]["karma"] = karma

    return list(tracks_map.values())

def
get_scorable_track_data
(
session
,
redis_instance
,
strategy
)
:
score_params
=
strategy
.
get_score_params
(
)
S
=
score_params
[
"S"
]
r
=
score_params
[
"r"
]
q
=
score_params
[
"q"
]
o
=
score_params
[
"o"
]
f
=
score_params
[
"f"
]
qr
=
score_params
[
"qr"
]
xf
=
score_params
[
"xf"
]
pt
=
score_params
[
"pt"
]
trending_key
=
make_trending_cache_key
(
"week"
,
,
strategy
.
version
)
track_ids
=
[
]
old_trending
=
get_pickled_key
(
redis_instance
,
trending_key
)
if
old_trending
:
track_ids
=
old_trending
[
1
]
exclude_track_ids
=
track_ids
[
:
qr
]
# Get followers
follower_query
=
(
session
.
query
(
Follow
.
followee_user_id
.
label
(
"user_id"
)
,
User
.
is_verified
.
label
(
"is_verified"
)
,
func
.
count
(
Follow
.
followee_user_id
)
.
label
(
"follower_count"
)
,
)
.
join
(
User
,
User
.
user_id
==
Follow
.
followee_user_id
)
.
filter
(
Follow
.
is_current
==
True
,
Follow
.
is_delete
==
False
,
User
.
is_current
==
True
,
Follow
.
created_at
<
(
datetime
.
now
(
)
timedelta
(
days
=
f
)
)
,
)
.
group_by
(
Follow
.
followee_user_id
,
User
.
is_verified
)
)
.
subquery
(
)
base_query
=
(
session
.
query
(
AggregatePlays
.
play_item_id
.
label
(
"track_id"
)
,
follower_query
.
c
.
user_id
,
follower_query
.
c
.
follower_count
,
AggregatePlays
.
count
,
Track
.
created_at
,
follower_query
.
c
.
is_verified
,
)
.
join
(
Track
,
Track
.
track_id
==
AggregatePlays
.
play_item_id
)
.
join
(
follower_query
,
follower_query
.
c
.
user_id
==
Track
.
owner_id
)
.
join
(
AggregateUser
,
AggregateUser
.
user_id
==
Track
.
owner_id
)
.
filter
(
Track
.
is_current
==
True
,
Track
.
is_delete
==
False
,
Track
.
is_unlisted
==
False
,
Track
.
stem_of
==
,
Track
.
track_id
.
notin_
(
exclude_track_ids
)
,
Track
.
created_at
>=
(
datetime
.
now
(
)
timedelta
(
days
=
o
)
)
,
follower_query
.
c
.
follower_count
<
S
,
follower_query
.
c
.
follower_count
>=
pt
,
AggregateUser
.
following_count
<
r
,
AggregatePlays
.
count
>=
q
,
)
)
.
all
(
)
tracks_map
=
{
record
[
0
]
:
{
"track_id"
:
record
[
0
]
,
"created_at"
:
record
[
4
]
.
isoformat
(
timespec
=
"seconds"
)
,
"owner_id"
:
record
[
1
]
,
"windowed_save_count"
:
0
,
"save_count"
:
0
,
"repost_count"
:
0
,
"windowed_repost_count"
:
0
,
"owner_follower_count"
:
record
[
2
]
,
"karma"
:
1
,
"listens"
:
record
[
3
]
,
"owner_verified"
:
record
[
5
]
,
}
for
record
in
base_query
}
track_ids
=
[
record
[
0
]
for
record
in
base_query
]
# Get all the extra values
repost_counts
=
get_repost_counts
(
session
,
False
,
False
,
track_ids
,
[
RepostType
.
track
]
)
windowed_repost_counts
=
get_repost_counts
(
session
,
False
,
False
,
track_ids
,
[
RepostType
.
track
]
,
,
"week"
)
save_counts
=
get_save_counts
(
session
,
False
,
False
,
track_ids
,
[
SaveType
.
track
]
)
windowed_save_counts
=
get_save_counts
(
session
,
False
,
False
,
track_ids
,
[
SaveType
.
track
]
,
,
"week"
)
karma_scores
=
get_karma
(
session
,
tuple
(
track_ids
)
,
strategy
,
,
False
,
xf
)
# Associate all the extra data
for
(
track_id
,
repost_count
)
in
repost_counts
:
tracks_map
[
track_id
]
[
"repost_count"
]
=
repost_count
for
(
track_id
,
repost_count
)
in
windowed_repost_counts
:
tracks_map
[
track_id
]
[
"windowed_repost_count"
]
=
repost_count
for
(
track_id
,
save_count
)
in
save_counts
:
tracks_map
[
track_id
]
[
"save_count"
]
=
save_count
for
(
track_id
,
save_count
)
in
windowed_save_counts
:
tracks_map
[
track_id
]
[
"windowed_save_count"
]
=
save_count
for
(
track_id
,
karma
)
in
karma_scores
:
tracks_map
[
track_id
]
[
"karma"
]
=
karma
return
list
(
tracks_map
.
values
(
)
)


def get_associated_user_id(args):
    """
    Returns a user_id the associated wallet

    Args:
        args: dict The parsed args from the request
        args.wallet: string The wallet to find associated with an user id

    Returns:
        number representing the user id
    """
    db = db_session.get_db_read_replica()
    with db.scoped_session() as session:
        user_id = (
            session.query(AssociatedWallet.user_id)
            .filter(AssociatedWallet.is_current == True)
            .filter(AssociatedWallet.is_delete == False)
            .filter(AssociatedWallet.wallet == args.get("wallet"))
            .first()
        )
        return user_id[0] if user_id else None

def get_associated_user_id ( args ) : db = db_session . get_db_read_replica ( ) with db . scoped_session ( ) as session : user_id = ( session . query ( AssociatedWallet . user_id ) . filter ( AssociatedWallet . is_current == True ) . filter ( AssociatedWallet . is_delete == False ) . filter ( AssociatedWallet . wallet == args . get ( "wallet" ) ) . first ( ) ) return user_id [ 0 ] if user_id else None


def get_unpopulated_tracks(
    session, track_ids, filter_deleted=False, filter_unlisted=True
):
    """
    Fetches tracks by checking the redis cache first then
    going to DB and writes to cache if not present

    Args:
        session: DB session
        track_ids: array A list of track ids

    Returns:
        Array of tracks
    """
    # Check the cached tracks
    cached_tracks_results = get_cached_tracks(track_ids)
    has_all_tracks_cached = cached_tracks_results.count(None) == 0
    if has_all_tracks_cached:
        res = cached_tracks_results
        if filter_deleted:
            res = list(filter(lambda track: not track["is_delete"], res))
        if filter_unlisted:
            res = list(filter(lambda track: not track["is_unlisted"], res))
        return res

    # Create a dict of cached tracks
    cached_tracks = {}
    for cached_track in cached_tracks_results:
        if cached_track:
            cached_tracks[cached_track["track_id"]] = cached_track

    track_ids_to_fetch = filter(
        lambda track_id: track_id not in cached_tracks, track_ids
    )

    tracks_query = (
        session.query(Track)
        .filter(Track.is_current == True, Track.stem_of == None)
        .filter(Track.track_id.in_(track_ids_to_fetch))
    )

    if filter_unlisted:
        tracks_query = tracks_query.filter(Track.is_unlisted == False)

    if filter_deleted:
        tracks_query = tracks_query.filter(Track.is_delete == False)

    tracks = tracks_query.all()
    tracks = helpers.query_result_to_list(tracks)
    queried_tracks = {track["track_id"]: track for track in tracks}

    # cache tracks for future use
    set_tracks_in_cache(tracks)

    tracks_response = []
    for track_id in track_ids:
        if track_id in cached_tracks:
            if filter_unlisted and cached_tracks[track_id]["is_unlisted"]:
                continue
            if filter_deleted and cached_tracks[track_id]["is_delete"]:
                continue
            tracks_response.append(cached_tracks[track_id])
        elif track_id in queried_tracks:
            tracks_response.append(queried_tracks[track_id])

    return tracks_response

def
get_unpopulated_tracks
(
session
,
track_ids
,
filter_deleted
=
False
,
filter_unlisted
=
True
)
:
# Check the cached tracks
cached_tracks_results
=
get_cached_tracks
(
track_ids
)
has_all_tracks_cached
=
cached_tracks_results
.
count
(
)
==
0
if
has_all_tracks_cached
:
res
=
cached_tracks_results
if
filter_deleted
:
res
=
list
(
filter
(
lambda
track
:
not
track
[
"is_delete"
]
,
res
)
)
if
filter_unlisted
:
res
=
list
(
filter
(
lambda
track
:
not
track
[
"is_unlisted"
]
,
res
)
)
return
res
# Create a dict of cached tracks
cached_tracks
=
{
}
for
cached_track
in
cached_tracks_results
:
if
cached_track
:
cached_tracks
[
cached_track
[
"track_id"
]
]
=
cached_track
track_ids_to_fetch
=
filter
(
lambda
track_id
:
track_id
not
in
cached_tracks
,
track_ids
)
tracks_query
=
(
session
.
query
(
Track
)
.
filter
(
Track
.
is_current
==
True
,
Track
.
stem_of
==
)
.
filter
(
Track
.
track_id
.
in_
(
track_ids_to_fetch
)
)
)
if
filter_unlisted
:
tracks_query
=
tracks_query
.
filter
(
Track
.
is_unlisted
==
False
)
if
filter_deleted
:
tracks_query
=
tracks_query
.
filter
(
Track
.
is_delete
==
False
)
tracks
=
tracks_query
.
all
(
)
tracks
=
helpers
.
query_result_to_list
(
tracks
)
queried_tracks
=
{
track
[
"track_id"
]
:
track
for
track
in
tracks
}
# cache tracks for future use
set_tracks_in_cache
(
tracks
)
tracks_response
=
[
]
for
track_id
in
track_ids
:
if
track_id
in
cached_tracks
:
if
filter_unlisted
and
cached_tracks
[
track_id
]
[
"is_unlisted"
]
:
continue
if
filter_deleted
and
cached_tracks
[
track_id
]
[
"is_delete"
]
:
continue
tracks_response
.
append
(
cached_tracks
[
track_id
]
)
elif
track_id
in
queried_tracks
:
tracks_response
.
append
(
queried_tracks
[
track_id
]
)
return
tracks_response


Returns skipped transactions during indexing Takes query params blocknumber blockhash and transactionhash Filters by query params if they are not null

def check_skipped_transactions():
    """
    Returns skipped transactions during indexing
    Takes query params 'blocknumber', 'blockhash', and 'transactionhash'
    Filters by query params if they are not null
    """
    blocknumber = request.args.get("blocknumber", type=int)
    blockhash = request.args.get("blockhash")
    transactionhash = request.args.get("transactionhash")
    skipped_transactions = get_skipped_transactions(
        blocknumber, blockhash, transactionhash
    )
    return success_response(skipped_transactions)

def check_skipped_transactions ( ) : blocknumber = request . args . get ( "blocknumber" , type = int ) blockhash = request . args . get ( "blockhash" ) transactionhash = request . args . get ( "transactionhash" ) skipped_transactions = get_skipped_transactions ( blocknumber , blockhash , transactionhash ) return success_response ( skipped_transactions )


Returns whether a transaction PASSED | FAILED | NOT_FOUND based on all 3 query params blocknumber blockhash and transactionhash

def check_transaction_status():
    """
    Returns whether a transaction 'PASSED' | 'FAILED' | 'NOT_FOUND'
    based on all 3 query params 'blocknumber', 'blockhash', and 'transactionhash'
    """
    blocknumber = request.args.get("blocknumber", type=int)
    blockhash = request.args.get("blockhash")
    transactionhash = request.args.get("transactionhash")
    if blocknumber is None or blockhash is None or transactionhash is None:
        return error_response(
            "Please pass in required query parameters 'blocknumber', 'blockhash', and 'transactionhash'",
            400,
        )
    try:
        transaction_status = get_transaction_status(
            blocknumber, blockhash, transactionhash
        )
    except Exception as e:
        return error_response(e)
    return success_response(transaction_status)


max_drift : maximum duration in seconds between now and the latest recorded play record to be considered healthy

def play_check():
    """
    max_drift: maximum duration in seconds between `now` and the
     latest recorded play record to be considered healthy
    """
    max_drift = request.args.get("max_drift", type=int)

    latest_play = get_latest_play()
    drift = (datetime.now() - latest_play).total_seconds()

    # Error if max drift was provided and the drift is greater than max_drift
    error = max_drift and drift > max_drift

    return success_response(latest_play, 500 if error else 200, sign_response=False)


limit : number of latest plays to return max_drift : maximum duration in seconds between now and the latest recorded play record to be considered healthy

def sol_play_check():
    """
    limit: number of latest plays to return
    max_drift: maximum duration in seconds between `now` and the
    latest recorded play record to be considered healthy
    """
    limit = request.args.get("limit", type=int, default=20)
    max_drift = request.args.get("max_drift", type=int)
    error = None
    redis = redis_connection.get_redis()

    response = {}
    response = get_latest_sol_play_check_info(redis, limit)
    latest_db_sol_plays = response["latest_db_sol_plays"]

    if latest_db_sol_plays:
        latest_db_play = latest_db_sol_plays[0]
        latest_created_at = latest_db_play["created_at"]
        drift = (datetime.now() - latest_created_at).total_seconds()

        # Error if max drift was provided and the drift is greater than max_drift
        error = max_drift and drift > max_drift

    return success_response(response, 500 if error else 200, sign_response=False)


def get_tracks_including_unlisted(args):
    """Fetch a track, allowing unlisted.

    Args:
        args: dict
        args.identifiers: array of { handle, id, url_title} dicts
        args.current_user_id: optional current user ID
        args.filter_deleted: filter deleted tracks
        args.with_users: include users in unlisted tracks
    """
    tracks = []
    identifiers = args["identifiers"]
    for i in identifiers:
        helpers.validate_arguments(i, ["handle", "id", "url_title"])

    current_user_id = args.get("current_user_id")
    db = get_db_read_replica()
    with db.scoped_session() as session:

        def get_unpopulated_track():
            base_query = session.query(Track)
            filter_cond = []

            # Create filter conditions as a list of `and` clauses
            for i in identifiers:
                filter_cond.append(
                    and_(Track.is_current == True, Track.track_id == i["id"])
                )

            # Pass array of `and` clauses into an `or` clause as destructured *args
            base_query = base_query.filter(or_(*filter_cond))

            # Allow filtering of deletes
            # Note: There is no standard for boolean url parameters, and any value (including 'false')
            # will be evaluated as true, so an explicit check is made for true
            if "filter_deleted" in args:
                filter_deleted = args.get("filter_deleted")
                if filter_deleted:
                    base_query = base_query.filter(Track.is_delete == False)

            # Perform the query
            # TODO: pagination is broken with unlisted tracks
            query_results = paginate_query(base_query).all()
            tracks = helpers.query_result_to_list(query_results)

            # Mapping of track_id -> track object from request;
            # used to check route_id when iterating through identifiers
            identifiers_map = {track["id"]: track for track in identifiers}

            # If the track is unlisted and the generated route_id does not match the route_id in db,
            # filter track out from response
            def filter_fn(track):
                input_track = identifiers_map[track["track_id"]]
                route_id = helpers.create_track_route_id(
                    input_track["url_title"], input_track["handle"]
                )

                return not track["is_unlisted"] or track["route_id"] == route_id

            tracks = list(filter(filter_fn, tracks))

            track_ids = list(map(lambda track: track["track_id"], tracks))
            return (tracks, track_ids)

        key = make_cache_key(args)
        (tracks, track_ids) = use_redis_cache(
            key, UNPOPULATED_TRACK_CACHE_DURATION_SEC, get_unpopulated_track
        )

        # Add users
        if args.get("with_users", False):
            user_id_list = get_users_ids(tracks)
            users = get_users_by_id(session, user_id_list, current_user_id)
            for track in tracks:
                user = users[track["owner_id"]]
                if user:
                    track["user"] = user
        # Populate metadata
        tracks = populate_track_metadata(session, track_ids, tracks, current_user_id)

    return tracks


Returns whether a given user_balance needs update . Very heuristic - y : - If we ve never updated before ( new balance entry ) update now - If the balance has not been updated in BALANCE_REFRESH seconds

def does_user_balance_need_refresh(user_balance: UserBalance) -> bool:
    """Returns whether a given user_balance needs update.
    Very heuristic-y:
        - If we've never updated before (new balance entry), update now
        - If the balance has not been updated in BALANCE_REFRESH seconds
    """

    if user_balance.updated_at == user_balance.created_at:
        return True

    delta = timedelta(seconds=BALANCE_REFRESH)
    needs_refresh = user_balance.updated_at < (datetime.now() - delta)
    return needs_refresh

def does_user_balance_need_refresh ( user_balance : UserBalance ) -> bool : if user_balance . updated_at == user_balance . created_at : return True delta = timedelta ( seconds = BALANCE_REFRESH ) needs_refresh = user_balance . updated_at < ( datetime . now ( ) - delta ) return needs_refresh


Gets user balances . Returns mapping { user_id : balance } Enqueues in Redis user balances requiring refresh .

def get_balances(session: Session, redis: Redis, user_ids: List[int]):
    """Gets user balances.
    Returns mapping { user_id: balance }
    Enqueues in Redis user balances requiring refresh.
    """
    # Find user balances
    query: List[UserBalance] = (
        (session.query(UserBalance)).filter(UserBalance.user_id.in_(user_ids)).all()
    )

    # Construct result dict from query result
    result = {
        user_balance.user_id: {
            "owner_wallet_balance": user_balance.balance,
            "associated_wallets_balance": user_balance.associated_wallets_balance,
            "associated_sol_wallets_balance": user_balance.associated_sol_wallets_balance,
            "waudio_balance": user_balance.waudio,
            "total_balance": str(
                int(user_balance.balance)
                + int(user_balance.associated_wallets_balance)
                + int(user_balance.associated_sol_wallets_balance)
                * 10 ** WAUDIO_DECIMALS
                + int(user_balance.waudio) * 10 ** WAUDIO_DECIMALS
            ),
        }
        for user_balance in query
    }

    # Find user_ids that don't yet have a balance
    user_ids_set = set(user_ids)
    fetched_user_ids_set = {x.user_id for x in query}
    needs_balance_set = user_ids_set - fetched_user_ids_set

    # Add new balances to result set
    no_balance_dict = {
        user_id: {
            "owner_wallet_balance": "0",
            "associated_wallets_balance": "0",
            "associated_sol_wallets_balance": "0",
            "total_balance": "0",
            "waudio_balance": "0",
        }
        for user_id in needs_balance_set
    }
    result.update(no_balance_dict)

    # Get old balances that need refresh
    needs_refresh = [
        user_balance.user_id
        for user_balance in query
        if does_user_balance_need_refresh(user_balance)
    ]

    # Enqueue new balances to Redis refresh queue
    # 1. All users who need a new balance
    # 2. All users who need a balance refresh
    enqueue_lazy_balance_refresh(redis, list(needs_balance_set) + needs_refresh)

    return result


Query all users with cnode_endpoint_string in replica set If replica_type = ReplicaType . PRIMARY - > returns users with cnode_endpoint_string as primary Else if replica_type = ReplicaType . SECONDARY - > returns users with cnode_endpoint_string as secondary1 or secondary2 Else ( only other option is replica_type = ReplicaType . ALL )

def get_users_cnode(cnode_endpoint_string, replica_type=ReplicaType.PRIMARY):
    """
    Query all users with `cnode_endpoint_string` in replica set
    If replica_type=ReplicaType.PRIMARY -> returns users with `cnode_endpoint_string` as primary
    Else if replica_type=ReplicaType.SECONDARY -> returns users with `cnode_endpoint_string` as secondary1 or secondary2
    Else (only other option is replica_type=ReplicaType.ALL)

    Only returns values where 1/2 secondaries are non-null
    """
    users = []
    db = get_db_read_replica()
    with db.scoped_session() as session:
        users_res = sqlalchemy.text(
            f"""
            SELECT
            *
            FROM
            (
                SELECT
                "user_id",
                "wallet",
                ("creator_node_endpoints") [1] as "primary",
                ("creator_node_endpoints") [2] as "secondary1",
                ("creator_node_endpoints") [3] as "secondary2",
                "primary_id" as "primarySpID",
                ("secondary_ids") [1] as "secondary1SpID",
                ("secondary_ids") [2] as "secondary2SpID"
                FROM
                (
                    SELECT
                    "user_id",
                    "wallet",
                    string_to_array("creator_node_endpoint", ',') as "creator_node_endpoints",
                    "primary_id",
                    "secondary_ids"
                    FROM
                    "users"
                    WHERE
                    "creator_node_endpoint" IS NOT NULL
                    AND "is_current" IS TRUE
                    ORDER BY
                    "user_id" ASC
                ) as "s"
            ) as "t"
            WHERE
            {
                "t.primary = :cnode_endpoint_string AND"
                if replica_type == ReplicaType.PRIMARY
                else '(t.secondary1 = :cnode_endpoint_string OR t.secondary2 = :cnode_endpoint_string) AND'
                if replica_type == ReplicaType.SECONDARY
                else '(t.primary = :cnode_endpoint_string OR '
                    't.secondary1 = :cnode_endpoint_string OR '
                    't.secondary2 = :cnode_endpoint_string) AND'
            }
            t.secondary1 is not NULL;
            """
        )
        users = session.execute(
            users_res, {"cnode_endpoint_string": cnode_endpoint_string}
        ).fetchall()
        users_dict = [dict(row) for row in users]
    return users_dict


Fetches the ids of the trending tracks using the route s cache

def get_trending_ids(args, strategy):
    """
    Fetches the ids of the trending tracks using the route's cache

    Args:
        args: (dict) The args of the request
        args.limit: (number) The number of track ids to return
        args.genre: (string?) The genre to fetch the trending tracks for
        strategy: (string?) The strategy to apply to compute trending

    Returns:
        trending_times_id: (dict) Dictionary containing the week/month/year trending track ids
    """

    cache_args = {}
    limit = args["limit"]
    if "genre" in args:
        cache_args["genre"] = args["genre"]

    week_trending_track_ids = get_time_trending(cache_args, "week", limit, strategy)
    month_trending_track_ids = get_time_trending(cache_args, "month", limit, strategy)
    year_trending_track_ids = get_time_trending(cache_args, "year", limit, strategy)

    return {
        "week": week_trending_track_ids,
        "month": month_trending_track_ids,
        "year": year_trending_track_ids,
    }

def get_trending_ids ( args , strategy ) : cache_args = { } limit = args [ "limit" ] if "genre" in args : cache_args [ "genre" ] = args [ "genre" ] week_trending_track_ids = get_time_trending ( cache_args , "week" , limit , strategy ) month_trending_track_ids = get_time_trending ( cache_args , "month" , limit , strategy ) year_trending_track_ids = get_time_trending ( cache_args , "year" , limit , strategy ) return { "week" : week_trending_track_ids , "month" : month_trending_track_ids , "year" : year_trending_track_ids , }


Calculates the scores of related artists to the given user_id by querying who followers of the user_id also follow and using the scoring algorithm :

def _calculate_related_artists_scores(
    session: Session, user_id, sample_size=None, limit=MAX_RELATED_ARTIST_COUNT
) -> List[RelatedArtist]:
    """Calculates the scores of related artists to the given user_id by
    querying who followers of the user_id also follow and using the scoring algorithm:

    `score = mutual_follower_count * percentage_of_suggested_artist_followers`
    """

    # Get all the followers of the artist
    followers_subquery = aliased(
        Follow,
        session.query(Follow.follower_user_id)
        .filter(
            Follow.followee_user_id == user_id,
            Follow.is_current,
            Follow.is_delete == False,
        )
        .subquery(name="followers"),
    )
    if sample_size is None:
        followers_sampled = aliased(Follow)
    else:
        followers_sampled = aliased(
            Follow, tablesample(cast(FromClause, Follow), func.system_rows(sample_size))
        )

    # Find out who the followers are following
    mutual_followers_subquery = (
        session.query(
            followers_sampled.followee_user_id.label("suggested_artist_id"),
            func.count(followers_subquery.follower_user_id).label(
                "mutual_follower_count"
            ),
        )
        .select_from(followers_subquery)
        .join(
            followers_sampled,
            followers_subquery.follower_user_id == followers_sampled.follower_user_id,
        )
        .filter(
            followers_sampled.is_current,
            followers_sampled.is_delete == False,
            followers_sampled.followee_user_id != user_id,
        )
        .group_by(followers_sampled.followee_user_id)
        .subquery(name="mutual_followers")
    )

    # Score the artists gathered from the above queries by:
    #
    #       score = mutual_follower_count * percentage_of_suggested_artist_followers
    #
    # Examples:
    #
    # If we're finding related artists to artist A and artist B shares 20 followers
    # with artist A, and 50% of artist B's following are followers of artist A, then
    # the score for artist B is 20 * 0.50 = 10.
    #
    # If artists A and C share 1000 followers but C has 100,000 followers total, then
    # that's only 1% of artist C's following. Artist C gets a score of 1,000 * 0.01 = 10
    #
    scoring_query = (
        session.query(
            User.user_id.label("related_artist_user_id"),
            func.round(
                1.0
                * column("mutual_follower_count")
                * column("mutual_follower_count")
                / AggregateUser.follower_count,
                3,
            ).label("score"),
        )
        .select_from(mutual_followers_subquery)
        .join(AggregateUser, AggregateUser.user_id == column("suggested_artist_id"))
        .join(User, User.user_id == column("suggested_artist_id"))
        .filter(
            User.is_current,
            AggregateUser.track_count > 0,
            # Should never be true, but occasionally this is the mutual artist's
            # first follow and aggregate user hasn't updated yet
            AggregateUser.follower_count > 0,
        )
        .order_by(desc(column("score")), User.user_id)
        .limit(limit)
    )
    rows = scoring_query.all()
    related_artists = [
        RelatedArtist(
            user_id=user_id,
            related_artist_user_id=row.related_artist_user_id,
            score=row.score,
        )
        for row in rows
    ]
    return related_artists


Checks to make sure the user specified has at least a minimum required number of followers and that they don t already have fresh score calculation and then if necessary calculates new related artist scores .

def update_related_artist_scores_if_needed(
    session: Session, user_id: int
) -> Tuple[bool, str]:
    """Checks to make sure the user specified has at least a minimum required number of followers,
    and that they don't already have fresh score calculation,
    and then if necessary calculates new related artist scores.

    Args:
        session (Session): the db sesssion to use for the connection
        user_id (int): the user_id of the user of which we're finding related artists

    Returns:
        bool: whether an update was needed
        str: the reason why an update was not needed, if applicable
    """

    # Filter by followers first, since that narrows down more users
    aggregate_user = (
        session.query(AggregateUser)
        .filter(AggregateUser.user_id == user_id)
        .one_or_none()
    )
    related_artists = []
    # Don't calculate if there's not enough followers
    if not aggregate_user or aggregate_user.follower_count < MIN_FOLLOWER_REQUIREMENT:
        return False, "Not enough followers"
    existing_score = (
        session.query(RelatedArtist).filter(RelatedArtist.user_id == user_id).first()
    )
    # Don't recalculate if we already have recently
    if (
        existing_score
        and existing_score.created_at > datetime.utcnow() - CALCULATION_TTL
    ):
        return (
            False,
            f"Fresh calculation already exists. created_at={existing_score.created_at}",
        )
    # Use table sampling if more than a certain number of followers
    if aggregate_user.follower_count >= MAX_FOLLOWERS_WITHOUT_SAMPLE:
        related_artists = _calculate_related_artists_scores(
            session, user_id, SAMPLE_SIZE_ROWS
        )
    else:
        related_artists = _calculate_related_artists_scores(session, user_id)
    if related_artists:
        session.query(RelatedArtist).filter(RelatedArtist.user_id == user_id).delete()
        session.bulk_save_objects(related_artists)
        return True, ""
    return False, "No results"


Confirms that a transaction is causing an error indexing across the discovery nodes Gets all other discovery nodes and makes an api call to check the status of a transaction given a blocknumber blockhash and transactionhash

def confirm_indexing_transaction_error(
    redis, blocknumber, blockhash, transactionhash, message
):
    """
    Confirms that a transaction is causing an error indexing across the discovery nodes
    Gets all other discovery nodes and makes an api call to check the status of a transaction
    given a blocknumber, blockhash, and transactionhash
    """
    all_other_nodes = get_all_other_nodes()[0]
    num_other_nodes = len(all_other_nodes)
    num_transaction_failures = 0
    for node in all_other_nodes:
        try:
            endpoint = f"{node}/indexing/transaction_status?blocknumber={blocknumber}&blockhash={blockhash}&transactionhash={transactionhash}"
            response = requests.get(endpoint, timeout=10)
            if response.status_code != 200:
                raise Exception(
                    f"Query to indexing transaction status endpoint {endpoint} \
                    failed with status code {response.status_code}"
                )
            if response.json()["data"] == "FAILED":
                num_transaction_failures += 1
        except Exception as e:
            logger.error(e)

    # Mark the redis indexing error w/ has_consensus = true so that it skips this transaction
    if (
        num_other_nodes >= 1
        and num_transaction_failures
        >= num_other_nodes * INDEXING_FAILURE_CONSENSUS_PERCENT
    ):
        set_indexing_error(
            redis, blocknumber, blockhash, transactionhash, message, True
        )