Example #1
0
def get_playlist_repost_intersection_users(repost_playlist_id,
                                           follower_user_id):
    users = []
    db = get_db()
    with db.scoped_session() as session:
        # ensure playlist_id exists
        playlist_entry = session.query(Playlist).filter(
            Playlist.playlist_id == repost_playlist_id,
            Playlist.is_current == True).first()
        if playlist_entry is None:
            return api_helpers.error_response(
                'Resource not found for provided playlist id', 404)

        query = (session.query(User).filter(
            User.is_current == True, User.is_ready == True,
            User.user_id.in_(
                session.query(Repost.user_id).filter(
                    Repost.repost_item_id == repost_playlist_id,
                    Repost.repost_type != RepostType.track,
                    Repost.is_current == True,
                    Repost.is_delete == False).intersect(
                        session.query(Follow.followee_user_id).filter(
                            Follow.follower_user_id == follower_user_id,
                            Follow.is_current == True,
                            Follow.is_delete == False)))))
        users = paginate_query(query).all()
        users = helpers.query_result_to_list(users)

    return api_helpers.success_response(users)
Example #2
0
def get_playlists():
    playlists = []
    current_user_id = get_current_user_id(required=False)
    filter_out_private_playlists = True

    db = get_db()
    with db.scoped_session() as session:
        try:
            playlist_query = (session.query(Playlist).filter(
                Playlist.is_current == True))

            # playlist ids filter if the optional query param is passed in
            if "playlist_id" in request.args:
                playlist_id_str_list = request.args.getlist("playlist_id")
                playlist_id_list = []
                try:
                    playlist_id_list = [int(y) for y in playlist_id_str_list]
                    playlist_query = playlist_query.filter(
                        Playlist.playlist_id.in_(playlist_id_list))
                except ValueError as e:
                    raise exceptions.ArgumentError(
                        "Invalid value found in playlist id list", e)

            if "user_id" in request.args:
                user_id = request.args.get("user_id", type=int)
                # user id filter if the optional query param is passed in
                playlist_query = playlist_query.filter(
                    Playlist.playlist_owner_id == user_id)

                # if the current user is the same as the user passed in through the query param then we're trying
                # to get playlists for, check if the users are the same. if they are the same, the current user is
                # trying to request their own playlists, so allow them to see private playlists
                if current_user_id and user_id and (int(current_user_id)
                                                    == int(user_id)):
                    filter_out_private_playlists = False

            if filter_out_private_playlists:
                playlist_query = playlist_query.filter(
                    Playlist.is_private == False)

            playlist_query = playlist_query.order_by(desc(Playlist.created_at))
            playlists = paginate_query(playlist_query).all()
            playlists = helpers.query_result_to_list(playlists)

            # retrieve playlist ids list
            playlist_ids = list(
                map(lambda playlist: playlist["playlist_id"], playlists))

            current_user_id = get_current_user_id(required=False)

            # bundle peripheral info into playlist results
            playlists = populate_playlist_metadata(
                session, playlist_ids, playlists,
                [RepostType.playlist, RepostType.album],
                [SaveType.playlist, SaveType.album], current_user_id)

        except sqlalchemy.orm.exc.NoResultFound:
            pass

    return api_helpers.success_response(playlists)
Example #3
0
def get_playlist_repost_intersection_users(repost_playlist_id,
                                           follower_user_id):
    users = []
    db = get_db_read_replica()
    with db.scoped_session() as session:
        # ensure playlist_id exists
        playlist_entry = (session.query(Playlist).filter(
            Playlist.playlist_id == repost_playlist_id,
            Playlist.is_current == True).first())
        if playlist_entry is None:
            raise exceptions.NotFoundError(
                "Resource not found for provided playlist id")

        query = session.query(User).filter(
            User.is_current == True,
            User.user_id.in_(
                session.query(Repost.user_id).filter(
                    Repost.repost_item_id == repost_playlist_id,
                    Repost.repost_type != RepostType.track,
                    Repost.is_current == True,
                    Repost.is_delete == False,
                ).intersect(
                    session.query(Follow.followee_user_id).filter(
                        Follow.follower_user_id == follower_user_id,
                        Follow.is_current == True,
                        Follow.is_delete == False,
                    ))),
        )
        users = paginate_query(query).all()
        users = helpers.query_result_to_list(users)

    return users
Example #4
0
def get_follow_intersection_users(followee_user_id, follower_user_id):
    users = []
    db = get_db()
    with db.scoped_session() as session:
        query = (session.query(User).filter(
            User.is_current == True, User.is_ready == True,
            User.user_id.in_(
                session.query(Follow.follower_user_id).filter(
                    Follow.followee_user_id == followee_user_id,
                    Follow.is_current == True,
                    Follow.is_delete == False).intersect(
                        session.query(Follow.followee_user_id).filter(
                            Follow.follower_user_id == follower_user_id,
                            Follow.is_current == True,
                            Follow.is_delete == False)))))
        users = paginate_query(query).all()
        users = helpers.query_result_to_list(users)
        user_ids = [user[response_name_constants.user_id] for user in users]

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into user results
        users = populate_user_metadata(session, user_ids, users,
                                       current_user_id)

        # order by follower_count desc
        users.sort(
            key=lambda user: user[response_name_constants.follower_count],
            reverse=True)

    return api_helpers.success_response(users)
Example #5
0
def get_followers_for_user(followee_user_id):
    users = []
    db = get_db()
    with db.scoped_session() as session:
        # correlated subquery sqlalchemy code:
        # https://groups.google.com/forum/#!topic/sqlalchemy/WLIy8jxD7qg
        inner_follow = aliased(Follow)
        outer_follow = aliased(Follow)

        # subquery to get a user's follower count
        inner_select = (session.query(func.count(
            inner_follow.followee_user_id)).filter(
                inner_follow.is_current == True,
                inner_follow.is_delete == False, inner_follow.followee_user_id
                == outer_follow.follower_user_id).correlate(outer_follow))

        # get all users that follow input user, sorted by their follower count desc
        outer_select = (
            session.query(
                outer_follow.follower_user_id,
                inner_select.as_scalar().label(
                    response_name_constants.follower_count)).filter(
                        outer_follow.followee_user_id == followee_user_id,
                        outer_follow.is_current == True,
                        outer_follow.is_delete == False).
            group_by(outer_follow.follower_user_id).order_by(
                response_name_constants.follower_count + " desc",
                # secondary sort to guarantee determinism as explained here:
                # https://stackoverflow.com/questions/13580826/postgresql-repeating-rows-from-limit-offset
                asc(outer_follow.follower_user_id)))
        follower_user_ids_by_follower_count = paginate_query(
            outer_select).all()

        user_ids = [
            user_id for (user_id,
                         follower_count) in follower_user_ids_by_follower_count
        ]

        # get all users for above user_ids
        users = (session.query(User).filter(User.is_current == True,
                                            User.is_ready == True,
                                            User.user_id.in_(user_ids)).all())
        users = helpers.query_result_to_list(users)

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into user results
        users = populate_user_metadata(session, user_ids, users,
                                       current_user_id)

        # order by (follower_count desc, user_id asc) to match query sorting
        # tuple key syntax from: https://stackoverflow.com/a/4233482/8414360
        users.sort(key=lambda user:
                   (user[response_name_constants.follower_count],
                    (user['user_id']) * (-1)),
                   reverse=True)

    return api_helpers.success_response(users)
Example #6
0
def get_saves(save_type):
    save_query_type = None
    if save_type == 'albums':
        save_query_type = SaveType.album
    elif save_type == 'playlists':
        save_query_type = SaveType.playlist
    elif save_type == 'tracks':
        save_query_type = SaveType.track
    else:
        raise exceptions.ArgumentError("Invalid save type provided")

    save_results = []
    current_user_id = get_current_user_id()
    db = get_db()
    with db.scoped_session() as session:
        query = (
            session.query(Save)
            .filter(
                Save.user_id == current_user_id,
                Save.is_current == True,
                Save.is_delete == False,
                Save.save_type == save_query_type
            )
        )
        # filter out saves for deleted entries
        if save_type == 'albums':
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(Playlist.playlist_id).filter(
                        Playlist.is_album == True,
                        Playlist.is_current == True
                    )
                )
            )
        elif save_type == 'playlists':
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(Playlist.playlist_id).filter(
                        Playlist.is_album == False,
                        Playlist.is_current == True
                    )
                )
            )
        elif save_type == 'tracks':
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(Track.track_id).filter(
                        Track.is_current == True
                    )
                )
            )

        query_results = paginate_query(query).all()
        save_results = helpers.query_result_to_list(query_results)
    return api_helpers.success_response(save_results)
        def get_users_and_ids():

            can_use_shared_cache = (
                "id" in args and
                "is_creator" not in args and
                "wallet" not in args and
                "min_block_number" not in args and
                "handle" not in args
            )

            if can_use_shared_cache:
                users = get_unpopulated_users(session, args.get("id"))
                ids = list(map(lambda user: user["user_id"], users))
                return (users, ids)

            # Create initial query
            base_query = session.query(User)
            # Don't return the user if they have no wallet or handle (user creation did not finish properly on chain)
            base_query = base_query.filter(
                User.is_current == True, User.wallet != None, User.handle != None)

            # Process filters
            if "is_creator" in args:
                base_query = base_query.filter(User.is_creator == args.get("is_creator"))
            if "wallet" in args:
                wallet = args.get("wallet")
                wallet = wallet.lower()
                if len(wallet) == 42:
                    base_query = base_query.filter_by(wallet=wallet)
                    base_query = base_query.order_by(asc(User.created_at))
                else:
                    logger.warning("Invalid wallet length")
            if "handle" in args:
                handle = args.get("handle").lower()
                base_query = base_query.filter_by(handle_lc=handle)

            # Conditionally process an array of users
            if "id" in args:
                user_id_list = args.get("id")
                try:
                    base_query = base_query.filter(User.user_id.in_(user_id_list))
                except ValueError as e:
                    raise exceptions.ArgumentError(
                        "Invalid value found in user id list", e)
            if "min_block_number" in args:
                base_query = base_query.filter(
                    User.blocknumber >= args.get("min_block_number")
                )
            users = paginate_query(base_query).all()
            users = helpers.query_result_to_list(users)

            user_ids = list(map(lambda user: user["user_id"], users))

            return (users, user_ids)
Example #8
0
def get_reposters_for_track(repost_track_id):
    user_results = []
    db = get_db_read_replica()
    with db.scoped_session() as session:
        # Ensure Track exists for provided repost_track_id.
        track_entry = session.query(Track).filter(
            Track.track_id == repost_track_id,
            Track.is_current == True).first()
        if track_entry is None:
            raise exceptions.NotFoundError(
                'Resource not found for provided track id')

        # Subquery to get all (user_id, follower_count) entries from Follows table.
        follower_count_subquery = (session.query(
            Follow.followee_user_id,
            func.count(Follow.followee_user_id).label(
                response_name_constants.follower_count)).filter(
                    Follow.is_current == True,
                    Follow.is_delete == False).group_by(
                        Follow.followee_user_id).subquery())

        # Get all Users that reposted track, ordered by follower_count desc & paginated.
        query = (
            session.query(
                User,
                # Replace null values from left outer join with 0 to ensure sort works correctly.
                (func.coalesce(follower_count_subquery.c.follower_count, 0)
                 ).label(response_name_constants.follower_count))
            # Left outer join to associate users with their follower count.
            .outerjoin(
                follower_count_subquery,
                follower_count_subquery.c.followee_user_id ==
                User.user_id).filter(
                    User.is_current == True,
                    # Only select users that reposted given track.
                    User.user_id.in_(
                        session.query(Repost.user_id).filter(
                            Repost.repost_item_id == repost_track_id,
                            Repost.repost_type == RepostType.track,
                            Repost.is_current == True,
                            Repost.is_delete == False)
                    )).order_by(desc(response_name_constants.follower_count)))
        user_results = paginate_query(query).all()

        # Fix format to return only Users objects with follower_count field.
        if user_results:
            users, follower_counts = zip(*user_results)
            user_results = helpers.query_result_to_list(users)
            for i, user in enumerate(user_results):
                user[response_name_constants.
                     follower_count] = follower_counts[i]
    return user_results
Example #9
0
def get_users():
    users = []
    db = get_db()
    with db.scoped_session() as session:
        # Create initial query
        base_query = session.query(User)
        # Don't return the user if they have no wallet or handle (user creation did not finish properly on chain)
        base_query = base_query.filter(User.is_current == True,
                                       User.wallet != None,
                                       User.handle != None)

        # Process filters
        if "is_creator" in request.args:
            is_creator_flag = request.args.get("is_creator") == "true"
            base_query = base_query.filter(User.is_creator == is_creator_flag)
        if "wallet" in request.args:
            wallet = request.args.get("wallet")
            wallet = wallet.lower()
            if len(wallet) == 42:
                base_query = base_query.filter_by(wallet=wallet)
            else:
                logger.warning("Invalid wallet length")
        if "handle" in request.args:
            handle = request.args.get("handle").lower()
            base_query = base_query.filter_by(handle_lc=handle)

        # Conditionally process an array of users
        if "id" in request.args:
            user_id_str_list = request.args.getlist("id")
            user_id_list = []
            try:
                user_id_list = [int(y) for y in user_id_str_list]
                base_query = base_query.filter(User.user_id.in_(user_id_list))
            except ValueError as e:
                raise exceptions.ArgumentError(
                    "Invalid value found in user id list", e)
        if "min_block_number" in request.args:
            min_block_number = request.args.get("min_block_number", type=int)
            base_query = base_query.filter(
                User.blocknumber >= min_block_number)
        users = paginate_query(base_query).all()
        users = helpers.query_result_to_list(users)

        user_ids = list(map(lambda user: user["user_id"], users))

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into user results
        users = populate_user_metadata(session, user_ids, users,
                                       current_user_id)

    return api_helpers.success_response(users)
Example #10
0
def get_followees_for_user(follower_user_id):
    users = []
    db = get_db()
    with db.scoped_session() as session:
        # correlated subquery sqlalchemy code:
        # https://groups.google.com/forum/#!topic/sqlalchemy/WLIy8jxD7qg
        inner_follow = aliased(Follow)
        outer_follow = aliased(Follow)

        # subquery to get a user's follower count
        inner_select = (session.query(func.count(
            inner_follow.followee_user_id)).filter(
                inner_follow.followee_user_id == outer_follow.followee_user_id,
                inner_follow.is_current == True,
                inner_follow.is_delete == False).correlate(outer_follow))

        # get all users followed by input user, sorted by their follower count desc
        outer_select = (session.query(
            outer_follow.followee_user_id,
            inner_select.as_scalar().label(
                response_name_constants.follower_count)).filter(
                    outer_follow.follower_user_id == follower_user_id,
                    outer_follow.is_current == True,
                    outer_follow.is_delete == False).group_by(
                        outer_follow.followee_user_id).order_by(
                            response_name_constants.follower_count + " desc"))
        followee_user_ids_by_follower_count = paginate_query(
            outer_select).all()

        user_ids = [
            user_id for (user_id,
                         follower_count) in followee_user_ids_by_follower_count
        ]

        # get all users for above user_ids
        users = (session.query(User).filter(User.is_current == True,
                                            User.is_ready == True,
                                            User.user_id.in_(user_ids)).all())
        users = helpers.query_result_to_list(users)

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into user results
        users = populate_user_metadata(session, user_ids, users,
                                       current_user_id)

        # order by follower_count desc
        users.sort(
            key=lambda user: user[response_name_constants.follower_count],
            reverse=True)

    return api_helpers.success_response(users)
Example #11
0
def get_ursm_cnodes(owner_wallet):
    db = get_db_read_replica()
    with db.scoped_session() as session:
        base_query = (session.query(URSMContentNode).filter(
            URSMContentNode.is_current == True, ).order_by(
                desc(URSMContentNode.cnode_sp_id)))
        if owner_wallet is not None:
            base_query = base_query.filter(
                URSMContentNode.owner_wallet == owner_wallet)
        query_results = paginate_query(base_query).all()
        ursm_content_nodes = helpers.query_result_to_list(query_results)

    return ursm_content_nodes
Example #12
0
def get_tracks():
    tracks = []
    db = get_db()
    with db.scoped_session() as session:
        # Create initial query
        base_query = session.query(Track)
        base_query = base_query.filter(Track.is_current == True)

        # Conditionally process an array of tracks
        if "id" in request.args:
            # Retrieve argument from flask request object
            # Ensures empty parameters are not processed
            track_id_str_list = request.args.getlist("id")
            track_id_list = []
            try:
                track_id_list = [int(y) for y in track_id_str_list]
                # Update query with track_id list
                base_query = base_query.filter(
                    Track.track_id.in_(track_id_list))
            except ValueError as e:
                logger.error("Invalid value found in track id list",
                             exc_info=True)
                raise e

        # Allow filtering of tracks by a certain creator
        if "user_id" in request.args:
            user_id = request.args.get("user_id", type=int)
            base_query = base_query.filter(Track.owner_id == user_id)

        if "min_block_number" in request.args:
            min_block_number = request.args.get("min_block_number", type=int)
            base_query = base_query.filter(
                Track.blocknumber >= min_block_number)

        whitelist_params = [
            'created_at', 'create_date', 'release_date', 'blocknumber',
            'track_id'
        ]
        base_query = parse_sort_param(base_query, Track, whitelist_params)
        query_results = paginate_query(base_query).all()
        tracks = helpers.query_result_to_list(query_results)

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

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into track results
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

    return api_helpers.success_response(tracks)
Example #13
0
        def get_unpopulated_playlists():
            playlist_query = session.query(Playlist).filter(
                Playlist.is_current == True)

            # playlist ids filter if the optional query param is passed in
            if "playlist_id" in args:
                playlist_id_list = args.get("playlist_id")
                try:
                    playlist_query = playlist_query.filter(
                        Playlist.playlist_id.in_(playlist_id_list))
                except ValueError as e:
                    raise exceptions.ArgumentError(
                        "Invalid value found in playlist id list", e)

            if "user_id" in args:
                user_id = args.get("user_id")
                # user id filter if the optional query param is passed in
                playlist_query = playlist_query.filter(
                    Playlist.playlist_owner_id == user_id)

            # If no current_user_id, never show hidden playlists
            if not current_user_id:
                playlist_query = playlist_query.filter(
                    Playlist.is_private == False)

            # Filter out deletes unless we're fetching explicitly by id
            if "playlist_id" not in args:
                playlist_query = playlist_query.filter(
                    Playlist.is_delete == False)

            playlist_query = playlist_query.order_by(desc(Playlist.created_at))
            playlists = paginate_query(playlist_query).all()
            playlists = helpers.query_result_to_list(playlists)

            # if we passed in a current_user_id, filter out all privte playlists where
            # the owner_id doesn't match the current_user_id
            if current_user_id:
                playlists = list(
                    filter(
                        lambda playlist: (not playlist["is_private"]) or
                        playlist["playlist_owner_id"] == current_user_id,
                        playlists,
                    ))

            # retrieve playlist ids list
            playlist_ids = list(
                map(lambda playlist: playlist["playlist_id"], playlists))

            return (playlists, playlist_ids)
Example #14
0
def get_reposters_for_track(repost_track_id):
    user_results = []
    db = get_db()
    with db.scoped_session() as session:
        # ensure track_id exists
        track_entry = session.query(Track).filter(
            Track.track_id == repost_track_id,
            Track.is_current == True
        ).first()
        if track_entry is None:
            return api_helpers.error_response('Resource not found for provided track id', 404)

        follower_count_subquery = (
            session.query(
                Follow.followee_user_id,
                func.count(Follow.followee_user_id).label(response_name_constants.follower_count)
            )
            .filter(
                Follow.is_current == True,
                Follow.is_delete == False
            )
            .group_by(Follow.followee_user_id)
            .order_by(response_name_constants.follower_count + " desc")
        )
        follower_count_subquery = paginate_query(follower_count_subquery).subquery()

        query = (
            session.query(User, follower_count_subquery.c.follower_count)
            .join(Repost, User.user_id == Repost.user_id)
            .outerjoin(follower_count_subquery, follower_count_subquery.c.followee_user_id == User.user_id)
            .filter(
                User.is_current == True,
                User.is_ready == True,
                Repost.repost_item_id == repost_track_id,
                Repost.repost_type == RepostType.track,
                Repost.is_current == True,
                Repost.is_delete == False
            )
        )
        user_results = query.all()

        if user_results:
            users, follower_counts = zip(*user_results)
            user_results = helpers.query_result_to_list(users)
            for i, user in enumerate(user_results):
                user[response_name_constants.follower_count] = follower_counts[i] or 0

    return api_helpers.success_response(user_results)
Example #15
0
def get_saves(save_type, user_id):
    save_query_type = None
    if save_type == "albums":
        save_query_type = SaveType.album
    elif save_type == "playlists":
        save_query_type = SaveType.playlist
    elif save_type == "tracks":
        save_query_type = SaveType.track
    else:
        raise exceptions.ArgumentError("Invalid save type provided")

    save_results = []
    db = get_db_read_replica()
    with db.scoped_session() as session:
        query = session.query(Save).filter(
            Save.user_id == user_id,
            Save.is_current == True,
            Save.is_delete == False,
            Save.save_type == save_query_type,
        )
        # filter out saves for deleted entries
        if save_type == "albums":
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(Playlist.playlist_id).filter(
                        Playlist.is_album == True,
                        Playlist.is_current == True)))
        elif save_type == "playlists":
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(Playlist.playlist_id).filter(
                        Playlist.is_album == False,
                        Playlist.is_current == True)))
        elif save_type == "tracks":
            query = query.filter(
                Save.save_item_id.in_(
                    session.query(
                        Track.track_id).filter(Track.is_current == True)))

        query_results = paginate_query(query).all()
        save_results = helpers.query_result_to_list(query_results)

    return save_results
Example #16
0
        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)
Example #17
0
def get_remix_track_parents(track_id, args):
    db = get_db_read_replica()
    with db.scoped_session() as session:
        base_query = (session.query(Track).join(
            Remix,
            and_(Remix.parent_track_id == Track.track_id,
                 Remix.child_track_id == track_id)).filter(
                     Track.is_current == True,
                     Track.is_unlisted == False).order_by(
                         desc(Track.created_at), desc(Track.track_id)))

        tracks = paginate_query(base_query).all()
        tracks = helpers.query_result_to_list(tracks)
        track_ids = list(map(lambda track: track["track_id"], tracks))
        current_user_id = get_current_user_id(required=False)
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

        if args.get("with_users", False):
            add_users_to_tracks(session, tracks)

    return tracks
Example #18
0
def _get_tips(session: Session, args: GetTipsArgs):
    UserTipAlias = aliased(UserTip)
    query: Query = session.query(UserTipAlias)
    has_pagination = False  # Keeps track if we already paginated

    if args.get("tx_signatures"):
        query = query.filter(UserTipAlias.signature.in_(args["tx_signatures"]))
    if args.get("receiver_min_followers", 0) > 0:
        query = query.join(
            AggregateUser,
            AggregateUser.user_id == UserTipAlias.receiver_user_id).filter(
                AggregateUser.follower_count >= args["receiver_min_followers"])

    if args.get("receiver_is_verified", False):
        query = query.join(
            User, User.user_id == UserTipAlias.receiver_user_id).filter(
                User.is_current == True, User.is_verified == True)
    if args.get("min_slot", 0) > 0:
        query = query.filter(UserTipAlias.slot >= args["min_slot"])
    if args.get("max_slot", 0) > 0:
        query = query.filter(UserTipAlias.slot <= args["max_slot"])
    if args.get("unique_by"):
        if args["unique_by"] == "sender":
            distinct_inner = (query.order_by(
                UserTipAlias.sender_user_id.asc(),
                UserTipAlias.slot.desc()).distinct(
                    UserTipAlias.sender_user_id).subquery())
            UserTipAlias = aliased(UserTip,
                                   distinct_inner,
                                   name="user_tips_uniqued")
            query = session.query(UserTipAlias)
        elif args["unique_by"] == "receiver":
            distinct_inner = (query.order_by(
                UserTipAlias.receiver_user_id.asc(),
                UserTipAlias.slot.desc()).distinct(
                    UserTipAlias.receiver_user_id).subquery())
            UserTipAlias = aliased(UserTip,
                                   distinct_inner,
                                   name="user_tips_uniqued")
            query = session.query(UserTipAlias)

    if args.get("user_id"):
        # We have to get the other users that this user follows for three potential uses:
        # 1) To filter tips to recipients the user follows (if necessary)
        # 2) To filter tips to senders the user follows (if necessary)
        # 3) To get the followees of the current user that have also tipped the receiver
        followees_query = (session.query(Follow.followee_user_id).filter(
            Follow.is_current == True,
            Follow.is_delete == False,
            Follow.follower_user_id == args["user_id"],
        ).cte("followees"))
        # First, filter the senders/receivers as necessary
        if args.get("current_user_follows"):
            FolloweesSender = aliased(followees_query,
                                      name="followees_for_sender")
            FolloweesReceiver = aliased(followees_query,
                                        name="followees_for_receiver")
            if args["current_user_follows"] == "receiver":
                query = query.join(
                    FolloweesReceiver,
                    UserTipAlias.receiver_user_id ==
                    FolloweesReceiver.c.followee_user_id,
                )
            elif args["current_user_follows"] == "sender":
                query = query.join(
                    FolloweesSender,
                    UserTipAlias.receiver_user_id ==
                    FolloweesSender.c.followee_user_id,
                )
            elif args["current_user_follows"] == "sender_or_receiver":
                query = query.outerjoin(
                    FolloweesSender,
                    UserTipAlias.sender_user_id ==
                    FolloweesSender.c.followee_user_id,
                )
                query = query.outerjoin(
                    FolloweesReceiver,
                    UserTipAlias.receiver_user_id ==
                    FolloweesReceiver.c.followee_user_id,
                )
                query = query.filter(
                    or_(
                        FolloweesSender.c.followee_user_id != None,
                        FolloweesReceiver.c.followee_user_id != None,
                    ))

        # Order and paginate before adding follower filters/aggregates
        query = query.order_by(UserTipAlias.slot.desc())
        query = paginate_query(query)

        # Get the tips for the user as a subquery
        # because now we need to get the other users that tipped that receiver
        # and joining on this already paginated/limited result will be much faster
        Tips = query.cte("tips")
        FolloweesAggregate = aliased(followees_query,
                                     name="followees_for_aggregate")

        # Get all of the followees joined on their aggregate user tips first
        # rather than joining each on the tips separately to help with speed
        FolloweeTippers = (session.query(
            AggregateUserTips.sender_user_id,
            AggregateUserTips.receiver_user_id,
            FolloweesAggregate.c.followee_user_id,
        ).select_from(FolloweesAggregate).outerjoin(
            AggregateUserTips,
            AggregateUserTips.sender_user_id ==
            FolloweesAggregate.c.followee_user_id,
        ).cte("followee_tippers"))
        # Now we have the tips listed multiple times, one for each followee sender.
        # So group by the tip and aggregate up the followee sender IDs into a list
        query = (session.query(
            UserTip, func.array_agg(FolloweeTippers.c.sender_user_id)
        ).select_entity_from(Tips).outerjoin(
            FolloweeTippers,
            FolloweeTippers.c.receiver_user_id == Tips.c.receiver_user_id,
        ).group_by(Tips))
        has_pagination = True

    query = query.order_by(UserTipAlias.slot.desc())
    if not has_pagination:
        query = paginate_query(query)

    tips_results: List[UserTip] = query.all()
    return tips_results
Example #19
0
def get_feed_sql(args):
    feed_results = []
    db = get_db_read_replica()

    feed_filter = args.get("filter")
    # Allow for fetching only tracks
    tracks_only = args.get("tracks_only", False)

    followee_user_ids = args.get("followee_user_ids", [])

    # Current user - user for whom feed is being generated
    current_user_id = args.get("user_id")
    with db.scoped_session() as session:
        # Generate list of users followed by current user, i.e. 'followees'
        if not followee_user_ids:
            followee_user_ids = (
                session.query(Follow.followee_user_id)
                .filter(
                    Follow.follower_user_id == current_user_id,
                    Follow.is_current == True,
                    Follow.is_delete == False,
                )
                .all()
            )
            followee_user_ids = [f[0] for f in followee_user_ids]

        # Fetch followee creations if requested
        if feed_filter in ["original", "all"]:
            if not tracks_only:
                # Query playlists posted by followees, sorted and paginated by created_at desc
                created_playlists_query = (
                    session.query(Playlist)
                    .filter(
                        Playlist.is_current == True,
                        Playlist.is_delete == False,
                        Playlist.is_private == False,
                        Playlist.playlist_owner_id.in_(followee_user_ids),
                    )
                    .order_by(desc(Playlist.created_at))
                )
                created_playlists = paginate_query(created_playlists_query, False).all()

                # get track ids for all tracks in playlists
                playlist_track_ids = set()
                for playlist in created_playlists:
                    for track in playlist.playlist_contents["track_ids"]:
                        playlist_track_ids.add(track["track"])

                # get all track objects for track ids
                playlist_tracks = get_unpopulated_tracks(session, playlist_track_ids)
                playlist_tracks_dict = {
                    track["track_id"]: track for track in playlist_tracks
                }

                # get all track ids that have same owner as playlist and created in "same action"
                # "same action": track created within [x time] before playlist creation
                tracks_to_dedupe = set()
                for playlist in created_playlists:
                    for track_entry in playlist.playlist_contents["track_ids"]:
                        track = playlist_tracks_dict.get(track_entry["track"])
                        if not track:
                            continue
                        max_timedelta = datetime.timedelta(
                            minutes=trackDedupeMaxMinutes
                        )
                        if (
                            (track["owner_id"] == playlist.playlist_owner_id)
                            and (track["created_at"] <= playlist.created_at)
                            and (
                                playlist.created_at - track["created_at"]
                                <= max_timedelta
                            )
                        ):
                            tracks_to_dedupe.add(track["track_id"])
                tracks_to_dedupe = list(tracks_to_dedupe)
            else:
                # No playlists to consider
                tracks_to_dedupe = []
                created_playlists = []

            # Query tracks posted by followees, sorted & paginated by created_at desc
            # exclude tracks that were posted in "same action" as playlist
            created_tracks_query = (
                session.query(Track)
                .filter(
                    Track.is_current == True,
                    Track.is_delete == False,
                    Track.is_unlisted == False,
                    Track.stem_of == None,
                    Track.owner_id.in_(followee_user_ids),
                    Track.track_id.notin_(tracks_to_dedupe),
                )
                .order_by(desc(Track.created_at))
            )
            created_tracks = paginate_query(created_tracks_query, False).all()

            # extract created_track_ids and created_playlist_ids
            created_track_ids = [track.track_id for track in created_tracks]
            created_playlist_ids = [
                playlist.playlist_id for playlist in created_playlists
            ]

        # Fetch followee reposts if requested
        if feed_filter in ["repost", "all"]:
            # query items reposted by followees, sorted by oldest followee repost of item;
            # paginated by most recent repost timestamp
            repost_subquery = session.query(Repost).filter(
                Repost.is_current == True,
                Repost.is_delete == False,
                Repost.user_id.in_(followee_user_ids),
            )
            # exclude items also created by followees to guarantee order determinism, in case of "all" filter
            if feed_filter == "all":
                repost_subquery = repost_subquery.filter(
                    or_(
                        and_(
                            Repost.repost_type == RepostType.track,
                            Repost.repost_item_id.notin_(created_track_ids),
                        ),
                        and_(
                            Repost.repost_type != RepostType.track,
                            Repost.repost_item_id.notin_(created_playlist_ids),
                        ),
                    )
                )
            repost_subquery = repost_subquery.subquery()

            repost_query = (
                session.query(
                    repost_subquery.c.repost_item_id,
                    repost_subquery.c.repost_type,
                    func.min(repost_subquery.c.created_at).label("min_created_at"),
                )
                .group_by(
                    repost_subquery.c.repost_item_id, repost_subquery.c.repost_type
                )
                .order_by(desc("min_created_at"))
            )
            followee_reposts = paginate_query(repost_query, False).all()

            # build dict of track_id / playlist_id -> oldest followee repost timestamp from followee_reposts above
            track_repost_timestamp_dict = {}
            playlist_repost_timestamp_dict = {}
            for (
                repost_item_id,
                repost_type,
                oldest_followee_repost_timestamp,
            ) in followee_reposts:
                if repost_type == RepostType.track:
                    track_repost_timestamp_dict[
                        repost_item_id
                    ] = oldest_followee_repost_timestamp
                elif repost_type in (RepostType.playlist, RepostType.album):
                    playlist_repost_timestamp_dict[
                        repost_item_id
                    ] = oldest_followee_repost_timestamp

            # extract reposted_track_ids and reposted_playlist_ids
            reposted_track_ids = list(track_repost_timestamp_dict.keys())
            reposted_playlist_ids = list(playlist_repost_timestamp_dict.keys())

            # Query tracks reposted by followees
            reposted_tracks = session.query(Track).filter(
                Track.is_current == True,
                Track.is_delete == False,
                Track.is_unlisted == False,
                Track.stem_of == None,
                Track.track_id.in_(reposted_track_ids),
            )
            # exclude tracks already fetched from above, in case of "all" filter
            if feed_filter == "all":
                reposted_tracks = reposted_tracks.filter(
                    Track.track_id.notin_(created_track_ids)
                )
            reposted_tracks = reposted_tracks.order_by(desc(Track.created_at)).all()

            if not tracks_only:
                # Query playlists reposted by followees, excluding playlists already fetched from above
                reposted_playlists = session.query(Playlist).filter(
                    Playlist.is_current == True,
                    Playlist.is_delete == False,
                    Playlist.is_private == False,
                    Playlist.playlist_id.in_(reposted_playlist_ids),
                )
                # exclude playlists already fetched from above, in case of "all" filter
                if feed_filter == "all":
                    reposted_playlists = reposted_playlists.filter(
                        Playlist.playlist_id.notin_(created_playlist_ids)
                    )
                reposted_playlists = reposted_playlists.order_by(
                    desc(Playlist.created_at)
                ).all()
            else:
                reposted_playlists = []

        if feed_filter == "original":
            tracks_to_process = created_tracks
            playlists_to_process = created_playlists
        elif feed_filter == "repost":
            tracks_to_process = reposted_tracks
            playlists_to_process = reposted_playlists
        else:
            tracks_to_process = created_tracks + reposted_tracks
            playlists_to_process = created_playlists + reposted_playlists

        tracks = helpers.query_result_to_list(tracks_to_process)
        playlists = helpers.query_result_to_list(playlists_to_process)

        # define top level feed activity_timestamp to enable sorting
        # activity_timestamp: created_at if item created by followee, else reposted_at
        for track in tracks:
            if track["owner_id"] in followee_user_ids:
                track[response_name_constants.activity_timestamp] = track["created_at"]
            else:
                track[
                    response_name_constants.activity_timestamp
                ] = track_repost_timestamp_dict[track["track_id"]]
        for playlist in playlists:
            if playlist["playlist_owner_id"] in followee_user_ids:
                playlist[response_name_constants.activity_timestamp] = playlist[
                    "created_at"
                ]
            else:
                playlist[
                    response_name_constants.activity_timestamp
                ] = playlist_repost_timestamp_dict[playlist["playlist_id"]]

        # bundle peripheral info into track and playlist objects
        track_ids = list(map(lambda track: track["track_id"], tracks))
        playlist_ids = list(map(lambda playlist: playlist["playlist_id"], playlists))
        tracks = populate_track_metadata(session, track_ids, tracks, current_user_id)
        playlists = populate_playlist_metadata(
            session,
            playlist_ids,
            playlists,
            [RepostType.playlist, RepostType.album],
            [SaveType.playlist, SaveType.album],
            current_user_id,
        )

        # build combined feed of tracks and playlists
        unsorted_feed = tracks + playlists

        # sort feed based on activity_timestamp
        sorted_feed = sorted(
            unsorted_feed,
            key=lambda entry: entry[response_name_constants.activity_timestamp],
            reverse=True,
        )

        # truncate feed to requested limit
        (limit, _) = get_pagination_vars()
        feed_results = sorted_feed[0:limit]
        if "with_users" in args and args.get("with_users") != False:
            user_id_list = get_users_ids(feed_results)
            users = get_users_by_id(session, user_id_list)
            for result in feed_results:
                if "playlist_owner_id" in result:
                    user = users[result["playlist_owner_id"]]
                    if user:
                        result["user"] = user
                elif "owner_id" in result:
                    user = users[result["owner_id"]]
                    if user:
                        result["user"] = user

    return feed_results
Example #20
0
def get_playlists(args):
    playlists = []
    current_user_id = get_current_user_id(required=False)
    filter_out_private_playlists = True

    db = get_db_read_replica()
    with db.scoped_session() as session:
        try:
            playlist_query = (
                session.query(Playlist)
                .filter(Playlist.is_current == True)
            )

            # playlist ids filter if the optional query param is passed in
            if "playlist_id" in args:
                playlist_id_list = args.get("playlist_id")
                try:
                    playlist_query = playlist_query.filter(Playlist.playlist_id.in_(playlist_id_list))
                except ValueError as e:
                    raise exceptions.ArgumentError("Invalid value found in playlist id list", e)

            if "user_id" in args:
                user_id = args.get("user_id")
                # user id filter if the optional query param is passed in
                playlist_query = playlist_query.filter(
                    Playlist.playlist_owner_id == user_id
                )

                # if the current user is the same as the user passed in through the query param then we're trying
                # to get playlists for, check if the users are the same. if they are the same, the current user is
                # trying to request their own playlists, so allow them to see private playlists
                if current_user_id and user_id and (int(current_user_id) == int(user_id)):
                    filter_out_private_playlists = False

            if filter_out_private_playlists:
                playlist_query = playlist_query.filter(
                    Playlist.is_private == False
                )

            # Filter out deletes unless we're fetching explicitly by id
            if "playlist_id" not in args:
                playlist_query = playlist_query.filter(
                    Playlist.is_delete == False
                )

            playlist_query = playlist_query.order_by(desc(Playlist.created_at))
            playlists = paginate_query(playlist_query).all()
            playlists = helpers.query_result_to_list(playlists)

            # retrieve playlist ids list
            playlist_ids = list(map(lambda playlist: playlist["playlist_id"], playlists))

            current_user_id = get_current_user_id(required=False)

            # bundle peripheral info into playlist results
            playlists = populate_playlist_metadata(
                session,
                playlist_ids,
                playlists,
                [RepostType.playlist, RepostType.album],
                [SaveType.playlist, SaveType.album],
                current_user_id
            )

            if args.get("with_users", False):
                user_id_list = get_users_ids(playlists)
                users = get_users_by_id(session, user_id_list)
                for playlist in playlists:
                    user = users[playlist['playlist_owner_id']]
                    if user:
                        playlist['user'] = user

        except sqlalchemy.orm.exc.NoResultFound:
            pass
    return playlists
Example #21
0
def get_tracks(args):
    tracks = []
    db = get_db_read_replica()
    with db.scoped_session() as session:
        # Create initial query
        base_query = session.query(Track)
        base_query = base_query.filter(Track.is_current == True,
                                       Track.is_unlisted == False,
                                       Track.stem_of == None)

        # Conditionally process an array of tracks
        if "id" in args:
            track_id_list = args.get("id")
            try:
                # Update query with track_id list
                base_query = base_query.filter(
                    Track.track_id.in_(track_id_list))
            except ValueError as e:
                logger.error("Invalid value found in track id list",
                             exc_info=True)
                raise e

        # Allow filtering of tracks by a certain creator
        if "user_id" in args:
            user_id = args.get("user_id")
            base_query = base_query.filter(Track.owner_id == user_id)

        # Allow filtering of deletes
        if "filter_deleted" in args:
            filter_deleted = args.get("filter_deleted")
            if filter_deleted:
                base_query = base_query.filter(Track.is_delete == False)

        if "min_block_number" in args:
            min_block_number = args.get("min_block_number")
            base_query = base_query.filter(
                Track.blocknumber >= min_block_number)

        whitelist_params = [
            'created_at', 'create_date', 'release_date', 'blocknumber',
            'track_id'
        ]
        base_query = parse_sort_param(base_query, Track, whitelist_params)
        query_results = paginate_query(base_query).all()
        tracks = helpers.query_result_to_list(query_results)

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

        current_user_id = get_current_user_id(required=False)

        # bundle peripheral info into track results
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)

        if args.get("with_users", False):
            user_id_list = get_users_ids(tracks)
            users = get_users_by_id(session, user_id_list)
            for track in tracks:
                user = users[track['owner_id']]
                if user:
                    track['user'] = user

    return tracks
Example #22
0
def get_repost_feed_for_user(user_id):
    feed_results = {}
    db = get_db()
    with db.scoped_session() as session:
        # query all reposts by user
        repost_query = (session.query(Repost).filter(
            Repost.is_current == True, Repost.is_delete == False,
            Repost.user_id == user_id).order_by(desc(Repost.created_at)))
        reposts = paginate_query(repost_query).all()

        # get track reposts from above
        track_reposts = [
            r for r in reposts if r.repost_type == RepostType.track
        ]

        # get reposted track ids
        repost_track_ids = [r.repost_item_id for r in track_reposts]

        # get playlist reposts from above
        playlist_reposts = [
            r for r in reposts if r.repost_type == RepostType.playlist
            or r.repost_type == RepostType.album
        ]

        # get reposted playlist ids
        repost_playlist_ids = [r.repost_item_id for r in playlist_reposts]

        track_reposts = helpers.query_result_to_list(track_reposts)
        playlist_reposts = helpers.query_result_to_list(playlist_reposts)

        # build track/playlist id --> repost dict from repost lists
        track_repost_dict = {
            repost["repost_item_id"]: repost
            for repost in track_reposts
        }
        playlist_repost_dict = {
            repost["repost_item_id"]: repost
            for repost in playlist_reposts
        }

        # query tracks for repost_track_ids
        track_query = (session.query(Track).filter(
            Track.is_current == True,
            Track.track_id.in_(repost_track_ids)).order_by(
                desc(Track.created_at)))
        tracks = paginate_query(track_query).all()
        tracks = helpers.query_result_to_list(tracks)

        # get track ids
        track_ids = [track["track_id"] for track in tracks]

        # query playlists for repost_playlist_ids
        playlist_query = (session.query(Playlist).filter(
            Playlist.is_current == True, Playlist.is_private == False,
            Playlist.playlist_id.in_(repost_playlist_ids)).order_by(
                desc(Playlist.created_at)))
        playlists = paginate_query(playlist_query).all()
        playlists = helpers.query_result_to_list(playlists)

        # get playlist ids
        playlist_ids = [playlist["playlist_id"] for playlist in playlists]

        # get repost counts by track and playlist IDs
        repost_counts = get_repost_counts(session, False, True,
                                          track_ids + playlist_ids, None)
        track_repost_counts = {
            repost_item_id: repost_count
            for (repost_item_id, repost_count, repost_type) in repost_counts
            if repost_type == RepostType.track
        }
        playlist_repost_counts = {
            repost_item_id: repost_count
            for (repost_item_id, repost_count, repost_type) in repost_counts
            if repost_type in (RepostType.playlist, RepostType.album)
        }

        # get save counts for tracks and playlists
        save_counts = get_save_counts(session, False, True,
                                      track_ids + playlist_ids, None)
        track_save_counts = {
            save_item_id: save_count
            for (save_item_id, save_count, save_type) in save_counts
            if save_type == SaveType.track
        }
        playlist_save_counts = {
            save_item_id: save_count
            for (save_item_id, save_count, save_type) in save_counts
            if save_type in (SaveType.playlist, SaveType.album)
        }

        current_user_id = get_current_user_id(required=False)
        requested_user_is_current_user = False
        user_reposted_track_ids = {}
        user_reposted_playlist_ids = {}
        user_saved_track_dict = {}
        user_saved_playlist_dict = {}
        followees_track_repost_dict = {}
        followees_playlist_repost_dict = {}
        if current_user_id:
            # if current user = user_id, skip current_user_reposted queries and default to true
            if current_user_id == user_id:
                requested_user_is_current_user = True
            else:
                user_reposted_query = (session.query(
                    Repost.repost_item_id, Repost.repost_type).filter(
                        Repost.is_current == True, Repost.is_delete == False,
                        Repost.user_id == current_user_id,
                        or_(Repost.repost_item_id.in_(track_ids),
                            Repost.repost_item_id.in_(playlist_ids))).all())

                # generate dictionary of track id --> current user reposted status
                user_reposted_track_ids = {
                    r[0]: True
                    for r in user_reposted_query if r[1] == RepostType.track
                }

                # generate dictionary of playlist id --> current user reposted status
                user_reposted_playlist_ids = {
                    r[0]: True
                    for r in user_reposted_query
                    if r[1] == RepostType.album or r[1] == RepostType.playlist
                }

            # build dict of tracks and playlists that current user has saved

            #   - query saves by current user from relevant tracks/playlists
            user_saved_query = (session.query(
                Save.save_item_id, Save.save_type).filter(
                    Save.is_current == True, Save.is_delete == False,
                    Save.user_id == current_user_id,
                    or_(Save.save_item_id.in_(track_ids),
                        Save.save_item_id.in_(playlist_ids))).all())
            #   - build dict of track id --> current user save status
            user_saved_track_dict = {
                save[0]: True
                for save in user_saved_query if save[1] == SaveType.track
            }
            #   - build dict of playlist id --> current user save status
            user_saved_playlist_dict = {
                save[0]: True
                for save in user_saved_query
                if save[1] == SaveType.playlist or save[1] == SaveType.album
            }

            # query current user's followees
            followee_user_ids = (session.query(Follow.followee_user_id).filter(
                Follow.follower_user_id == current_user_id,
                Follow.is_current == True, Follow.is_delete == False).all())
            followee_user_ids = [f[0] for f in followee_user_ids]

            # query all followees' reposts
            followee_repost_query = (session.query(Repost).filter(
                Repost.is_current == True, Repost.is_delete == False,
                Repost.user_id.in_(followee_user_ids),
                or_(Repost.repost_item_id.in_(repost_track_ids),
                    Repost.repost_item_id.in_(repost_playlist_ids))).order_by(
                        desc(Repost.created_at)))
            followee_reposts = paginate_query(followee_repost_query).all()
            followee_reposts = helpers.query_result_to_list(followee_reposts)

            # build dict of track id --> reposts from followee track reposts
            for repost in followee_reposts:
                if repost["repost_type"] == RepostType.track:
                    if repost[
                            "repost_item_id"] not in followees_track_repost_dict:
                        followees_track_repost_dict[
                            repost["repost_item_id"]] = []
                    followees_track_repost_dict[
                        repost["repost_item_id"]].append(repost)

            # build dict of playlist id --> reposts from followee playlist reposts
            for repost in followee_reposts:
                if (repost["repost_type"] == RepostType.playlist
                        or repost["repost_type"] == RepostType.album):
                    if repost[
                            "repost_item_id"] not in followees_playlist_repost_dict:
                        followees_playlist_repost_dict[
                            repost["repost_item_id"]] = []
                    followees_playlist_repost_dict[
                        repost["repost_item_id"]].append(repost)

        # populate metadata for track entries
        for track in tracks:
            track[response_name_constants.
                  repost_count] = track_repost_counts.get(
                      track["track_id"], 0)
            track[response_name_constants.save_count] = track_save_counts.get(
                track["track_id"], 0)
            track[response_name_constants.has_current_user_reposted] = (
                True if requested_user_is_current_user else
                user_reposted_track_ids.get(track["track_id"], False))
            track[response_name_constants.
                  has_current_user_saved] = user_saved_track_dict.get(
                      track["track_id"], False)
            track[response_name_constants.
                  followee_reposts] = followees_track_repost_dict.get(
                      track["track_id"], [])
            track[response_name_constants.
                  activity_timestamp] = track_repost_dict[
                      track["track_id"]]["created_at"]

        for playlist in playlists:
            playlist[response_name_constants.
                     repost_count] = playlist_repost_counts.get(
                         playlist["playlist_id"], 0)
            playlist[
                response_name_constants.save_count] = playlist_save_counts.get(
                    playlist["playlist_id"], 0)
            playlist[response_name_constants.has_current_user_reposted] = (
                True if requested_user_is_current_user else
                user_reposted_playlist_ids.get(playlist["playlist_id"], False))
            playlist[response_name_constants.has_current_user_saved] = \
                user_saved_playlist_dict.get(playlist["playlist_id"], False)
            playlist[response_name_constants.followee_reposts] = \
                followees_playlist_repost_dict.get(playlist["playlist_id"], [])
            playlist[response_name_constants.activity_timestamp] = \
                playlist_repost_dict[playlist["playlist_id"]]["created_at"]

        unsorted_feed = tracks + playlists

        # sort feed by repost timestamp desc
        feed_results = sorted(unsorted_feed,
                              key=lambda entry: entry[response_name_constants.
                                                      activity_timestamp],
                              reverse=True)

    return api_helpers.success_response(feed_results)
Example #23
0
        def get_tracks_and_ids():
            if "handle" in args:
                handle = args.get("handle")
                user_id = session.query(User.user_id).filter(
                    User.handle_lc == handle.lower()).first()
                args["user_id"] = user_id

            can_use_shared_cache = ("id" in args
                                    and not "min_block_number" in args
                                    and not "sort" in args
                                    and not "user_id" 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)

            # Create initial query
            base_query = session.query(Track)
            base_query = base_query.filter(Track.is_current == True,
                                           Track.is_unlisted == False,
                                           Track.stem_of == None)

            # Conditionally process an array of tracks
            if "id" in args:
                track_id_list = args.get("id")
                try:
                    # Update query with track_id list
                    base_query = base_query.filter(
                        Track.track_id.in_(track_id_list))
                except ValueError as e:
                    logger.error("Invalid value found in track id list",
                                 exc_info=True)
                    raise e

            # Allow filtering of tracks by a certain creator
            if "user_id" in args:
                user_id = args.get("user_id")
                base_query = base_query.filter(Track.owner_id == user_id)

            # Allow filtering of deletes
            if "filter_deleted" in args:
                filter_deleted = args.get("filter_deleted")
                if filter_deleted:
                    base_query = base_query.filter(Track.is_delete == False)

            if "min_block_number" in args:
                min_block_number = args.get("min_block_number")
                base_query = base_query.filter(
                    Track.blocknumber >= min_block_number)

            if "sort" in args:
                if args["sort"] == "date":
                    base_query = base_query.order_by(Track.created_at.desc(),
                                                     Track.track_id.desc())
                elif args["sort"] == "plays":
                    base_query = base_query.join(
                        AggregatePlays, AggregatePlays.play_item_id ==
                        Track.track_id).order_by(AggregatePlays.count.desc())
                else:
                    whitelist_params = [
                        'created_at', 'create_date', 'release_date',
                        'blocknumber', 'track_id'
                    ]
                    base_query = parse_sort_param(base_query, Track,
                                                  whitelist_params)
            query_results = paginate_query(base_query).all()
            tracks = helpers.query_result_to_list(query_results)

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

            return (tracks, track_ids)
Example #24
0
def get_feed():
    feed_results = []
    db = get_db()
    # Current user - user for whom feed is being generated
    current_user_id = get_current_user_id()
    with db.scoped_session() as session:
        # Generate list of users followed by current user, i.e. 'followees'
        followee_user_ids = (session.query(Follow.followee_user_id).filter(
            Follow.follower_user_id == current_user_id,
            Follow.is_current == True, Follow.is_delete == False).all())
        followee_user_ids = [f[0] for f in followee_user_ids]

        # Query playlists posted by followees, sorted and paginated by created_at desc
        created_playlists_query = (session.query(Playlist).filter(
            Playlist.is_current == True, Playlist.is_private == False,
            Playlist.playlist_owner_id.in_(followee_user_ids)).order_by(
                desc(Playlist.created_at)))
        created_playlists = paginate_query(created_playlists_query,
                                           False).all()

        # get track ids for all tracks in playlists
        playlist_track_ids = set()
        for playlist in created_playlists:
            for track in playlist.playlist_contents["track_ids"]:
                playlist_track_ids.add(track["track"])

        # get all track objects for track ids
        playlist_tracks = (session.query(Track).filter(
            Track.is_current == True,
            Track.track_id.in_(playlist_track_ids)).all())
        playlist_tracks_dict = {
            track.track_id: track
            for track in playlist_tracks
        }

        # get all track ids that have same owner as playlist and created in "same action"
        # "same action": track created within [x time] before playlist creation
        tracks_to_dedupe = set()
        for playlist in created_playlists:
            for track_entry in playlist.playlist_contents["track_ids"]:
                track = playlist_tracks_dict.get(track_entry["track"])
                if not track:
                    return api_helpers.error_response(
                        "Something caused the server to crash.")
                max_timedelta = datetime.timedelta(
                    minutes=trackDedupeMaxMinutes)
                if (track.owner_id == playlist.playlist_owner_id) and \
                    (track.created_at <= playlist.created_at) and \
                    (playlist.created_at - track.created_at <= max_timedelta):
                    tracks_to_dedupe.add(track.track_id)

        tracks_to_dedupe = list(tracks_to_dedupe)

        # Query tracks posted by followees, sorted & paginated by created_at desc
        # exclude tracks that were posted in "same action" as playlist
        created_tracks_query = (session.query(Track).filter(
            Track.is_current == True, Track.owner_id.in_(followee_user_ids),
            Track.track_id.notin_(tracks_to_dedupe)).order_by(
                desc(Track.created_at)))
        created_tracks = paginate_query(created_tracks_query, False).all()

        # extract created_track_ids and created_playlist_ids
        created_track_ids = [track.track_id for track in created_tracks]
        created_playlist_ids = [
            playlist.playlist_id for playlist in created_playlists
        ]

        # query items reposted by followees, sorted by oldest followee repost of item;
        # paginated by most recent repost timestamp
        # exclude items also created by followees to guarantee order determinism
        repost_subquery = (session.query(Repost).filter(
            Repost.is_current == True, Repost.is_delete == False,
            Repost.user_id.in_(followee_user_ids),
            or_(
                and_(Repost.repost_type == RepostType.track,
                     Repost.repost_item_id.notin_(created_track_ids)),
                and_(Repost.repost_type == RepostType.track,
                     Repost.repost_item_id.notin_(
                         created_playlist_ids)))).subquery())
        repost_query = (session.query(
            repost_subquery.c.repost_item_id, repost_subquery.c.repost_type,
            func.min(repost_subquery.c.created_at).label("min_created_at")
        ).group_by(
            repost_subquery.c.repost_item_id,
            repost_subquery.c.repost_type).order_by("min_created_at desc"))
        followee_reposts = paginate_query(repost_query, False).all()

        # build dict of track id -> oldest followee repost timestamp from followee_reposts above
        track_repost_timestamp_dict = {}
        playlist_repost_timestamp_dict = {}
        for (repost_item_id, repost_type,
             oldest_followee_repost_timestamp) in followee_reposts:
            if repost_type == RepostType.track:
                track_repost_timestamp_dict[
                    repost_item_id] = oldest_followee_repost_timestamp
            elif repost_type in (RepostType.playlist, RepostType.album):
                playlist_repost_timestamp_dict[
                    repost_item_id] = oldest_followee_repost_timestamp

        # extract reposted_track_ids and reposted_playlist_ids
        reposted_track_ids = list(track_repost_timestamp_dict.keys())
        reposted_playlist_ids = list(playlist_repost_timestamp_dict.keys())

        # Query tracks reposted by followees, excluding tracks already fetched from above
        reposted_tracks = (session.query(Track).filter(
            Track.is_current == True, Track.track_id.in_(reposted_track_ids),
            Track.track_id.notin_(created_track_ids)).order_by(
                desc(Track.created_at)).all())

        # Query playlists reposted by followees, excluding playlists already fetched from above
        reposted_playlists = (session.query(Playlist).filter(
            Playlist.is_current == True, Playlist.is_private == False,
            Playlist.playlist_id.in_(reposted_playlist_ids),
            Playlist.playlist_id.notin_(created_playlist_ids)).all())

        # Combine created + reposted track and playlist lists
        tracks = helpers.query_result_to_list(created_tracks + reposted_tracks)
        playlists = helpers.query_result_to_list(created_playlists +
                                                 reposted_playlists)

        # define top level feed activity_timestamp to enable sorting
        # activity_timestamp: created_at if item created by followee, else reposted_at
        for track in tracks:
            if track["owner_id"] in followee_user_ids:
                track[response_name_constants.
                      activity_timestamp] = track["created_at"]
            else:
                track[response_name_constants.
                      activity_timestamp] = track_repost_timestamp_dict[
                          track["track_id"]]
        for playlist in playlists:
            if playlist["playlist_owner_id"] in followee_user_ids:
                playlist[response_name_constants.
                         activity_timestamp] = playlist["created_at"]
            else:
                playlist[response_name_constants.activity_timestamp] = \
                    playlist_repost_timestamp_dict[playlist["playlist_id"]]

        # bundle peripheral info into track and playlist objects
        track_ids = list(map(lambda track: track["track_id"], tracks))
        playlist_ids = list(
            map(lambda playlist: playlist["playlist_id"], playlists))
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)
        playlists = populate_playlist_metadata(
            session, playlist_ids, playlists,
            [RepostType.playlist, RepostType.album],
            [SaveType.playlist, SaveType.album], current_user_id)

        # build combined feed of tracks and playlists
        unsorted_feed = tracks + playlists

        # sort feed based on activity_timestamp
        sorted_feed = sorted(unsorted_feed,
                             key=lambda entry: entry[response_name_constants.
                                                     activity_timestamp],
                             reverse=True)

        # truncate feed to requested limit
        (limit, _) = get_pagination_vars()
        feed_results = sorted_feed[0:limit]

    return api_helpers.success_response(feed_results)
def get_top_genre_users(args):
    genres = []
    if "genre" in args:
        genres = args.get("genre")

    # If the with_users url arg is provided, then populate the user metadata else return user ids
    with_users = args.get("with_users", False)

    db = get_db_read_replica()
    with db.scoped_session() as session:
        with_genres = len(genres) != 0

        # Associate the user w/ a genre by counting the total # of tracks per genre
        # taking the genre w/ the most tracks (using genre name as secondary sort)
        user_genre_count_query = (session.query(
            User.user_id.label('user_id'), Track.genre.label('genre'),
            func.row_number().over(
                partition_by=User.user_id,
                order_by=(desc(func.count(Track.genre)), asc(
                    Track.genre))).label("row_number")).join(
                        Track, Track.owner_id == User.user_id).filter(
                            User.is_current == True, User.is_creator == True,
                            Track.is_unlisted == False, Track.stem_of == None,
                            Track.is_current == True,
                            Track.is_delete == False).group_by(
                                User.user_id, Track.genre).order_by(
                                    desc(func.count(Track.genre)),
                                    asc(Track.genre)))

        user_genre_count_query = user_genre_count_query.subquery(
            'user_genre_count_query')

        user_genre_query = (session.query(
            user_genre_count_query.c.user_id.label('user_id'),
            user_genre_count_query.c.genre.label('genre'),
        ).filter(user_genre_count_query.c.row_number == 1).subquery(
            'user_genre_query'))

        # Using the subquery of user to associated genre,
        #   filter by the requested genres and
        #   sort by user follower count
        user_genre_followers_query = (
            session.query(user_genre_query.c.user_id.label('user_id')).join(
                Follow,
                Follow.followee_user_id == user_genre_query.c.user_id).filter(
                    Follow.is_current == True,
                    Follow.is_delete == False).group_by(
                        user_genre_query.c.user_id,
                        user_genre_query.c.genre).order_by(
                            # desc('follower_count')
                            desc(func.count(Follow.follower_user_id))))

        if with_genres:
            user_genre_followers_query = user_genre_followers_query.filter(
                user_genre_query.c.genre.in_(genres))

        # If the with_users flag is not set, respond with the user_ids
        users = paginate_query(user_genre_followers_query).all()
        user_ids = list(map(lambda user: user[0], users))

        # If the with_users flag is used, retrieve the user metadata
        if with_users:
            user_query = session.query(User).filter(User.user_id.in_(user_ids),
                                                    User.is_current == True)
            users = user_query.all()
            users = helpers.query_result_to_list(users)
            queried_user_ids = list(map(lambda user: user["user_id"], users))
            users = populate_user_metadata(session, queried_user_ids, users,
                                           None)

            # Sort the users so that it's in the same order as the previous query
            user_map = {user['user_id']: user for user in users}
            users = [user_map[user_id] for user_id in user_ids]
            return {'users': users}

        return {'user_ids': user_ids}
def get_repost_feed_for_user(user_id, args):
    feed_results = {}
    db = get_db_read_replica()
    current_user_id = args.get("current_user_id")

    with db.scoped_session() as session:
        if "handle" in args:
            handle = args.get("handle")
            user_id = session.query(
                User.user_id).filter(User.handle_lc == handle.lower()).first()

        # query all reposts by user
        repost_query = (session.query(Repost).filter(
            Repost.is_current == True, Repost.is_delete == False,
            Repost.user_id == user_id).order_by(desc(Repost.created_at),
                                                desc(Repost.repost_item_id),
                                                desc(Repost.repost_type)))

        reposts = paginate_query(repost_query).all()

        # get track reposts from above
        track_reposts = [
            r for r in reposts if r.repost_type == RepostType.track
        ]

        # get reposted track ids
        repost_track_ids = [r.repost_item_id for r in track_reposts]

        # get playlist reposts from above
        playlist_reposts = [
            r for r in reposts if r.repost_type == RepostType.playlist
            or r.repost_type == RepostType.album
        ]

        # get reposted playlist ids
        repost_playlist_ids = [r.repost_item_id for r in playlist_reposts]

        track_reposts = helpers.query_result_to_list(track_reposts)
        playlist_reposts = helpers.query_result_to_list(playlist_reposts)

        # build track/playlist id --> repost dict from repost lists
        track_repost_dict = {
            repost["repost_item_id"]: repost
            for repost in track_reposts
        }
        playlist_repost_dict = {
            repost["repost_item_id"]: repost
            for repost in playlist_reposts
        }

        # query tracks for repost_track_ids
        track_query = (session.query(Track).filter(
            Track.is_current == True, Track.is_delete == False,
            Track.is_unlisted == False, Track.stem_of == None,
            Track.track_id.in_(repost_track_ids)).order_by(
                desc(Track.created_at)))
        tracks = track_query.all()
        tracks = helpers.query_result_to_list(tracks)

        # get track ids
        track_ids = [track["track_id"] for track in tracks]

        # query playlists for repost_playlist_ids
        playlist_query = (session.query(Playlist).filter(
            Playlist.is_current == True, Playlist.is_delete == False,
            Playlist.is_private == False,
            Playlist.playlist_id.in_(repost_playlist_ids)).order_by(
                desc(Playlist.created_at)))
        playlists = playlist_query.all()
        playlists = helpers.query_result_to_list(playlists)

        # get playlist ids
        playlist_ids = [playlist["playlist_id"] for playlist in playlists]

        # populate full metadata
        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)
        playlists = populate_playlist_metadata(
            session, playlist_ids, playlists,
            [RepostType.playlist, RepostType.album],
            [SaveType.playlist, SaveType.album], current_user_id)

        # add activity timestamps
        for track in tracks:
            track[response_name_constants.
                  activity_timestamp] = track_repost_dict[
                      track["track_id"]]["created_at"]

        for playlist in playlists:
            playlist[response_name_constants.activity_timestamp] = \
                playlist_repost_dict[playlist["playlist_id"]]["created_at"]

        unsorted_feed = tracks + playlists

        # sort feed by repost timestamp desc
        feed_results = sorted(unsorted_feed,
                              key=lambda entry: entry[response_name_constants.
                                                      activity_timestamp],
                              reverse=True)

        if args.get("with_users", False):
            user_id_list = get_users_ids(feed_results)
            users = get_users_by_id(session, user_id_list)
            for result in feed_results:
                if 'playlist_owner_id' in result:
                    user = users[result['playlist_owner_id']]
                    if user:
                        result['user'] = user
                elif 'owner_id' in result:
                    user = users[result['owner_id']]
                    if user:
                        result['user'] = user

    return feed_results
def get_remixes_of(track_id, args):
    db = get_db_read_replica()
    with db.scoped_session() as session:
        # Fetch the parent track to get the track's owner id
        parent_track = session.query(Track).filter(
            Track.is_current == True,
            Track.track_id == track_id
        ).first()

        if parent_track == None:
            raise exceptions.ArgumentError("Invalid track_id provided")

        track_owner_id = parent_track.owner_id

        # Create subquery for save counts for sorting
        save_count_subquery = create_save_count_subquery(
            session, SaveType.track)

        # Create subquery for repost counts for sorting
        repost_count_subquery = create_repost_count_subquery(
            session, RepostType.track)

        # Get the 'children' remix tracks
        # Use the track owner id to fetch reposted/saved tracks returned first
        base_query = (
            session.query(
                Track
            )
            .join(
                Remix,
                and_(
                    Remix.child_track_id == Track.track_id,
                    Remix.parent_track_id == track_id
                )
            ).outerjoin(
                Save,
                and_(
                    Save.save_item_id == Track.track_id,
                    Save.save_type == SaveType.track,
                    Save.is_current == True,
                    Save.is_delete == False,
                    Save.user_id == track_owner_id
                )
            ).outerjoin(
                Repost,
                and_(
                    Repost.repost_item_id == Track.track_id,
                    Repost.user_id == track_owner_id,
                    Repost.repost_type == RepostType.track,
                    Repost.is_current == True,
                    Repost.is_delete == False
                )
            ).outerjoin(
                repost_count_subquery,
                repost_count_subquery.c['id'] == Track.track_id
            ).outerjoin(
                save_count_subquery,
                save_count_subquery.c['id'] == Track.track_id
            )
            .filter(
                Track.is_current == True,
                Track.is_delete == False,
                Track.is_unlisted == False
            )
            # 1. Co-signed tracks ordered by save + repost count
            # 2. Other tracks ordered by save + repost count
            .order_by(
                desc(
                    # If there is no "co-sign" for the track (no repost or save from the parent owner),
                    # defer to secondary sort
                    case(
                        [
                            (and_(Repost.created_at == None,
                                  Save.created_at == None), 0),
                        ],
                        else_=(
                            func.coalesce(repost_count_subquery.c.repost_count, 0) + \
                            func.coalesce(save_count_subquery.c.save_count, 0)
                        )
                    )
                ),
                # Order by saves + reposts
                desc(
                    func.coalesce(repost_count_subquery.c.repost_count, 0) + \
                    func.coalesce(save_count_subquery.c.save_count, 0)
                ),
                # Ties, pick latest track id
                desc(Track.track_id)
            )
        )

        (tracks, count) = paginate_query(base_query, True, True)
        tracks = tracks.all()
        tracks = helpers.query_result_to_list(tracks)
        track_ids = list(map(lambda track: track["track_id"], tracks))
        current_user_id = get_current_user_id(required=False)
        tracks = populate_track_metadata(
            session, track_ids, tracks, current_user_id)

        if args.get("with_users", False):
            add_users_to_tracks(session, tracks)

    return {'tracks': tracks, 'count': count}