Пример #1
0
def trending(time):
    (limit, offset) = get_pagination_vars()
    # Increment total trending count
    REDIS.incr(trending_cache_total_key, 1)

    genre = request.args.get("genre", default=None, type=str)
    if genre is None:
        redis_key = f"trending-{time}"
        redis_cache_value = REDIS.get(redis_key)
        if redis_cache_value is not None:
            json_cache = json.loads(redis_cache_value.decode('utf-8'))
            if json_cache is not None:
                num_cached_entries = len(json_cache['listen_counts'])
                logger.info(
                    f'Cache for {redis_key}, {num_cached_entries} entries, request limit {limit}'
                )
                if offset + limit <= num_cached_entries:
                    json_cache['listen_counts'] = json_cache['listen_counts'][
                        offset:offset + limit]
                    logger.info(f'Returning cache for {redis_key}')
                    # Increment cache hit count
                    REDIS.incr(trending_cache_hits_key, 1)
                    return api_helpers.success_response(json_cache)
    # Increment cache miss count
    REDIS.incr(trending_cache_miss_key, 1)
    # Recalculate trending values if necessary
    final_resp = generate_trending(get_db_read_replica(), time, genre, limit,
                                   offset)
    return api_helpers.success_response(final_resp)
Пример #2
0
def search(isAutocomplete):
    searchStr = request.args.get("query", type=str)
    if not searchStr:
        return api_helpers.error_response("Invalid value for parameter 'query'")
    searchStr = searchStr.replace('&', 'and')  # when creating query table, we substitute this too

    kind = request.args.get("kind", type=str, default="all")
    if kind not in SearchKind.__members__:
        return api_helpers.error_response(
            "Invalid value for parameter 'kind' must be in %s" % [k.name for k in SearchKind]
        )
    searchKind = SearchKind[kind]

    (limit, offset) = get_pagination_vars()

    results = {}
    if searchStr:
        db = get_db_read_replica()
        with db.scoped_session() as session:
            # Set similarity threshold to be used by % operator in queries.
            session.execute(sqlalchemy.text(f"select set_limit({minSearchSimilarity});"))

            if (searchKind in [SearchKind.all, SearchKind.tracks]):
                results['tracks'] = track_search_query(session, searchStr, limit, offset, False, isAutocomplete)
                results['saved_tracks'] = track_search_query(session, searchStr, limit, offset, True, isAutocomplete)
            if (searchKind in [SearchKind.all, SearchKind.users]):
                results['users'] = user_search_query(session, searchStr, limit, offset, False, isAutocomplete)
                results['followed_users'] = user_search_query(session, searchStr, limit, offset, True, isAutocomplete)
            if (searchKind in [SearchKind.all, SearchKind.playlists]):
                results['playlists'] = playlist_search_query(
                    session,
                    searchStr,
                    limit,
                    offset,
                    False,
                    False,
                    isAutocomplete
                )
                results['saved_playlists'] = playlist_search_query(
                    session,
                    searchStr,
                    limit,
                    offset,
                    False,
                    True,
                    isAutocomplete
                )
            if (searchKind in [SearchKind.all, SearchKind.albums]):
                results['albums'] = playlist_search_query(session, searchStr, limit, offset, True, False, isAutocomplete)
                results['saved_albums'] = playlist_search_query(
                    session,
                    searchStr,
                    limit,
                    offset,
                    True,
                    True,
                    isAutocomplete
                )

    return api_helpers.success_response(results)
Пример #3
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)

            (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)
Пример #4
0
def search(isAutocomplete):
    searchStr = request.args.get("query", type=str)
    if not searchStr:
        raise exceptions.ArgumentError("Invalid value for parameter 'query'")
    searchStr = searchStr.replace('&', 'and')  # when creating query table, we substitute this too

    (limit, offset) = get_pagination_vars()

    results = {
        'tracks': [],
        'users': [],
        'playlists': [],
        'albums': [],
        'saved_tracks': [],
        'followed_users': [],
        'saved_playlists': [],
        'saved_albums': [],
    }

    if searchStr:
        db = get_db()
        with db.scoped_session() as session:
            results['tracks'] = track_search_query(session, searchStr, limit, offset, False, isAutocomplete)
            results['users'] = user_search_query(session, searchStr, limit, offset, False, isAutocomplete)
            results['playlists'] = playlist_search_query(
                session,
                searchStr,
                limit,
                offset,
                False,
                False,
                isAutocomplete
            )
            results['albums'] = playlist_search_query(session, searchStr, limit, offset, True, False, isAutocomplete)

            results['saved_tracks'] = track_search_query(session, searchStr, limit, offset, True, isAutocomplete)
            results['followed_users'] = user_search_query(session, searchStr, limit, offset, True, isAutocomplete)
            results['saved_playlists'] = playlist_search_query(
                session,
                searchStr,
                limit,
                offset,
                False,
                True,
                isAutocomplete
            )
            results['saved_albums'] = playlist_search_query(
                session,
                searchStr,
                limit,
                offset,
                True,
                True,
                isAutocomplete
            )

    return api_helpers.success_response(results)
Пример #5
0
def get_feed(args):
    skip_es = request.args.get("es") == "0"
    use_es = es_url and not skip_es
    if use_es:
        try:
            (limit, _) = get_pagination_vars()
            return get_feed_es(args, limit)
        except:
            return get_feed_sql(args)
    else:
        return get_feed_sql(args)
Пример #6
0
def get_followees_for_user_route(follower_user_id):
    current_user_id = get_current_user_id(required=False)
    (limit, offset) = get_pagination_vars()
    args = {
        "follower_user_id": follower_user_id,
        "current_user_id": current_user_id,
        "limit": limit,
        "offset": offset,
    }
    users = get_followees_for_user(args)
    return api_helpers.success_response(users)
Пример #7
0
def get_followees_for_user_route(follower_user_id):
    current_user_id = get_current_user_id(required=False)
    (limit, offset) = get_pagination_vars()
    args = {
        'follower_user_id': follower_user_id,
        'current_user_id': current_user_id,
        'limit': limit,
        'offset': offset
    }
    users = get_followees_for_user(args)
    return api_helpers.success_response(users)
Пример #8
0
def get_remix_track_parents_route(track_id):
    args = to_dict(request.args)
    if "with_users" in request.args:
        args["with_users"] = parse_bool_param(request.args.get("with_users"))
    args["track_id"] = track_id
    args["current_user_id"] = get_current_user_id(required=False)
    limit, offset = get_pagination_vars()
    args["limit"] = limit
    args["offset"] = offset
    tracks = get_remix_track_parents(args)
    return api_helpers.success_response(tracks)
Пример #9
0
def get_user_history_route(user_id):
    try:
        (limit, offset) = get_pagination_vars()
        args = {
            "user_id": user_id,
            "limit": limit,
            "offset": offset,
        }
        user_history = get_user_history(args)
        return api_helpers.success_response(user_history)
    except exceptions.ArgumentError as e:
        return api_helpers.error_response(str(e), 400)
Пример #10
0
        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)
Пример #11
0
def get_savers_for_playlist_route(save_playlist_id):
    try:
        current_user_id = get_current_user_id(required=False)
        (limit, offset) = get_pagination_vars()
        args = {
            'save_playlist_id': save_playlist_id,
            'current_user_id': current_user_id,
            'limit': limit,
            'offset': offset
        }
        user_results = get_savers_for_playlist(args)
        return api_helpers.success_response(user_results)
    except exceptions.NotFoundError as e:
        return api_helpers.error_response(str(e), 404)
Пример #12
0
def get_savers_for_track_route(save_track_id):
    try:
        current_user_id = get_current_user_id(required=False)
        (limit, offset) = get_pagination_vars()
        args = {
            "save_track_id": save_track_id,
            "current_user_id": current_user_id,
            "limit": limit,
            "offset": offset,
        }
        user_results = get_savers_for_track(args)
        return api_helpers.success_response(user_results)
    except exceptions.NotFoundError as e:
        return api_helpers.error_response(str(e), 404)
Пример #13
0
def get_remixes_of_route(track_id):
    args = to_dict(request.args)
    args["track_id"] = track_id
    args["current_user_id"] = get_current_user_id(required=False)
    limit, offset = get_pagination_vars()
    args["limit"] = limit
    args["offset"] = offset
    if "with_users" in request.args:
        args["with_users"] = parse_bool_param(request.args.get("with_users"))
    try:
        remixes = get_remixes_of(args)
        return api_helpers.success_response(remixes)
    except exceptions.ArgumentError as e:
        return api_helpers.error_response(str(e), 400)
def get_trending_tracks(args):
    (limit, offset) = get_pagination_vars()
    current_user_id = get_current_user_id(required=False)

    db = get_db_read_replica()

    time = args.get('time')
    # Identity understands allTime as millennium.
    # TODO: Change this in https://github.com/AudiusProject/audius-protocol/pull/768/files
    query_time = time
    if time == 'allTime':
        query_time = 'millennium'

    with db.scoped_session() as session:
        trending_tracks = generate_trending(get_db_read_replica(), query_time,
                                            args.get('genre', None), limit,
                                            offset)

        track_scores = [
            z(time, track) for track in trending_tracks['listen_counts']
        ]
        sorted_track_scores = sorted(track_scores,
                                     key=lambda k: k['score'],
                                     reverse=True)

        track_ids = [track['track_id'] for track in sorted_track_scores]

        tracks = session.query(Track).filter(
            Track.is_current == True, Track.is_unlisted == False,
            Track.stem_of == None, Track.track_id.in_(track_ids)).all()
        tracks = helpers.query_result_to_list(tracks)

        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)
        tracks_map = {track['track_id']: track for track in tracks}

        # Re-sort the populated tracks b/c it loses sort order in sql query
        sorted_tracks = [tracks_map[track_id] for track_id in track_ids]

        if args.get("with_users", False):
            user_id_list = get_users_ids(sorted_tracks)
            users = get_users_by_id(session, user_id_list)
            for track in sorted_tracks:
                user = users[track['owner_id']]
                if user:
                    track['user'] = user
        return sorted_tracks
Пример #15
0
def get_playlist_tracks(args):
    playlists = []
    current_user_id = args.get("current_user_id")

    db = get_db_read_replica()
    with db.scoped_session() as session:
        try:
            playlist_id = args.get("playlist_id")
            playlist = (session.query(Playlist).filter(
                Playlist.is_current == True,
                Playlist.playlist_id == playlist_id).first())
            if playlist is None:
                return None

            playlist_track_ids = [
                track_id['track']
                for track_id in playlist.playlist_contents['track_ids']
            ]
            (limit, offset) = get_pagination_vars()
            query_playlist_track_ids = playlist_track_ids[offset:offset +
                                                          limit]

            playlist_tracks = (session.query(Track).filter(
                Track.is_current == True,
                Track.track_id.in_(query_playlist_track_ids)).all())

            tracks = helpers.query_result_to_list(playlist_tracks)
            tracks = populate_track_metadata(session, playlist_track_ids,
                                             tracks, current_user_id)

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

            tracks_dict = {track['track_id']: track for track in tracks}

            playlist_tracks = []
            for track_id in query_playlist_track_ids:
                playlist_tracks.append(tracks_dict[track_id])

            return playlist_tracks

        except sqlalchemy.orm.exc.NoResultFound:
            pass
    return playlists
Пример #16
0
def search_autocomplete():
    args = to_dict(request.args)
    validation_error = validate_search_args(args)
    if validation_error:
        return validation_error

    current_user_id = get_current_user_id(required=False)
    limit, offset = get_pagination_vars()
    search_args = {
        "is_auto_complete": True,
        "kind": args.get("kind", "all"),
        "query": args.get("query"),
        "current_user_id": current_user_id,
        "with_users": False,
        "limit": limit,
        "offset": offset
    }
    resp = search(search_args)
    return api_helpers.success_response(resp)
def get_trending_tracks(args):
    (limit, offset) = get_pagination_vars()
    current_user_id = get_current_user_id(required=False)

    db = get_db_read_replica()
    time = args.get('time')
    with db.scoped_session() as session:
        trending_tracks = generate_trending(get_db_read_replica(), time,
                                            args.get('genre', None), limit,
                                            offset)

        track_scores = [
            z(time, track) for track in trending_tracks['listen_counts']
        ]
        sorted_track_scores = sorted(track_scores,
                                     key=lambda k: k['score'],
                                     reverse=True)

        track_ids = [track['track_id'] for track in sorted_track_scores]

        tracks = session.query(Track).filter(
            Track.is_current == True, Track.is_unlisted == False,
            Track.stem_of == None, Track.track_id.in_(track_ids)).all()
        tracks = helpers.query_result_to_list(tracks)

        tracks = populate_track_metadata(session, track_ids, tracks,
                                         current_user_id)
        tracks_map = {track['track_id']: track for track in tracks}

        # Re-sort the populated tracks b/c it loses sort order in sql query
        sorted_tracks = [tracks_map[track_id] for track_id in track_ids]

        if args.get("with_users", False):
            user_id_list = get_users_ids(sorted_tracks)
            users = get_users_by_id(session, user_id_list)
            for track in sorted_tracks:
                user = users[track['owner_id']]
                if user:
                    track['user'] = user
        return sorted_tracks
Пример #18
0
def search_tags():
    search_str = request.args.get("query", type=str)
    current_user_id = get_current_user_id(required=False)
    if not search_str:
        raise exceptions.ArgumentError("Invalid value for parameter 'query'")

    user_tag_count = request.args.get("user_tag_count", type=str)
    if not user_tag_count:
        user_tag_count = "2"

    kind = request.args.get("kind", type=str, default="all")
    validSearchKinds = [SearchKind.all, SearchKind.tracks, SearchKind.users]
    try:
        searchKind = SearchKind[kind]
        if searchKind not in validSearchKinds:
            raise Exception
    except Exception:
        return api_helpers.error_response(
            "Invalid value for parameter 'kind' must be in %s" %
            [k.name for k in validSearchKinds], 400)

    results = {}

    (limit, offset) = get_pagination_vars()
    like_tags_str = str.format('%{}%', search_str)
    db = get_db_read_replica()
    with db.scoped_session() as session:
        if (searchKind in [SearchKind.all, SearchKind.tracks]):
            track_res = sqlalchemy.text(f"""
                select distinct(track_id)
                from
                (
                    select
                        strip(to_tsvector(tracks.tags)) as tagstrip,
                        track_id
                    from
                        tracks
                    where
                        (tags like :like_tags_query)
                        and (is_current is true)
                        and (is_delete is false)
                        and (is_unlisted is false)
                        and (stem_of is NULL)
                    order by
                        updated_at desc
                ) as t
                    where
                    tagstrip @@ to_tsquery(:query);
                """)
            track_ids = session.execute(track_res, {
                "query": search_str,
                "like_tags_query": like_tags_str
            }).fetchall()

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

            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_(track_ids),
            ).all())

            tracks = helpers.query_result_to_list(tracks)
            track_play_counts = get_track_play_counts(track_ids)

            tracks = populate_track_metadata(session, track_ids, tracks,
                                             current_user_id)

            for track in tracks:
                track_id = track["track_id"]
                track[response_name_constants.
                      play_count] = track_play_counts.get(track_id, 0)

            play_count_sorted_tracks = \
                sorted(
                    tracks, key=lambda i: i[response_name_constants.play_count], reverse=True)

            # Add pagination parameters to track and user results
            play_count_sorted_tracks = \
                play_count_sorted_tracks[slice(offset, offset + limit, 1)]

            results['tracks'] = play_count_sorted_tracks

        if (searchKind in [SearchKind.all, SearchKind.users]):
            user_res = sqlalchemy.text(f"""
                select * from
                (
                    select
                        count(track_id),
                        owner_id
                    from
                    (
                        select
                            strip(to_tsvector(tracks.tags)) as tagstrip,
                            track_id,
                            owner_id
                        from
                            tracks
                        where
                            (tags like :like_tags_query)
                            and (is_current is true)
                            and (is_unlisted is false)
                            and (stem_of is NULL)
                        order by
                            updated_at desc
                    ) as t
                    where
                            tagstrip @@ to_tsquery(:query)
                    group by
                            owner_id
                    order by
                            count desc
                ) as usr
                where
                    usr.count >= :user_tag_count;
                """)
            user_ids = session.execute(
                user_res, {
                    "query": search_str,
                    "like_tags_query": like_tags_str,
                    "user_tag_count": user_tag_count
                }).fetchall()

            # user_ids is list of tuples - simplify to 1-D list
            user_ids = [i[1] 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,
                                           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(offset, offset + limit, 1)]

            results['users'] = followee_sorted_users

    # Add personalized results for a given user
    if current_user_id:
        if (searchKind in [SearchKind.all, SearchKind.tracks]):
            # Query saved tracks for the current user that contain this tag
            saves_query = (session.query(Save.save_item_id).filter(
                Save.is_current == True, Save.is_delete == False,
                Save.save_type == SaveType.track,
                Save.user_id == current_user_id,
                Save.save_item_id.in_(track_ids)).all())
            saved_track_ids = [i[0] for i in saves_query]
            saved_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_(saved_track_ids),
            ).all())
            saved_tracks = helpers.query_result_to_list(saved_tracks)
            for saved_track in saved_tracks:
                saved_track_id = saved_track["track_id"]
                saved_track[response_name_constants.play_count] = \
                    track_play_counts.get(saved_track_id, 0)
            saved_tracks = \
                populate_track_metadata(
                    session, saved_track_ids, saved_tracks, current_user_id)

            # Sort and paginate
            play_count_sorted_saved_tracks = \
                sorted(
                    saved_tracks, key=lambda i: i[response_name_constants.play_count], reverse=True)

            play_count_sorted_saved_tracks = \
                play_count_sorted_saved_tracks[slice(
                    offset, offset + limit, 1)]

            results['saved_tracks'] = play_count_sorted_saved_tracks

        if (searchKind in [SearchKind.all, SearchKind.users]):
            # Query followed users that have referenced this tag
            followed_user_query = (session.query(
                Follow.followee_user_id).filter(
                    Follow.is_current == True, Follow.is_delete == False,
                    Follow.follower_user_id == current_user_id,
                    Follow.followee_user_id.in_(user_ids)).all())
            followed_user_ids = [i[0] for i in followed_user_query]
            followed_users = (session.query(User).filter(
                User.is_current == True,
                User.user_id.in_(followed_user_ids)).all())
            followed_users = helpers.query_result_to_list(followed_users)
            followed_users = \
                populate_user_metadata(
                    session,
                    followed_user_ids,
                    followed_users,
                    current_user_id
                )

            followed_users_followee_sorted = \
                sorted(
                    followed_users,
                    key=lambda i: i[response_name_constants.follower_count],
                    reverse=True)

            followed_users_followee_sorted = \
                followed_users_followee_sorted[slice(
                    offset, offset + limit, 1)]

            results['followed_users'] = followed_users_followee_sorted

    return api_helpers.success_response(results)
Пример #19
0
def trending(time):
    identity_url = shared_config['discprov']['identity_service_url']
    identity_trending_endpoint = urljoin(identity_url,
                                         f"/tracks/trending/{time}")

    (limit, offset) = get_pagination_vars()
    queryparams = {}
    queryparams["limit"] = limit
    queryparams["offset"] = offset

    resp = None
    try:
        resp = requests.get(identity_trending_endpoint, params=queryparams)
    except Exception as e:
        logger.error(
            f'Error retrieving trending info - {identity_trending_endpoint}, {queryparams}'
        )
        raise e

    json_resp = resp.json()
    if "error" in json_resp:
        return api_helpers.error_response(json_resp["error"], 500)

    listen_counts = json_resp["listenCounts"]
    # Convert trackId to snakeCase
    for track_entry in listen_counts:
        track_entry[response_name_constants.track_id] = track_entry['trackId']
        del track_entry['trackId']

    track_ids = [
        track[response_name_constants.track_id] for track in listen_counts
    ]

    db = get_db()
    with db.scoped_session() as session:
        # Filter tracks to not-deleted ones so trending order is preserved
        not_deleted_track_ids = (session.query(Track.track_id).filter(
            Track.track_id.in_(track_ids), Track.is_current == True,
            Track.is_delete == False).all())
        not_deleted_track_ids = set(
            [record[0] for record in not_deleted_track_ids])

        # Query repost counts
        repost_counts = get_repost_counts(session, False, True,
                                          not_deleted_track_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
        }

        # Query follower info for each track owner
        # Query each track owner
        track_owners_query = (session.query(
            Track.track_id, Track.owner_id).filter(
                Track.is_current == True,
                Track.track_id.in_(not_deleted_track_ids)).all())

        # Generate track_id <-> owner_id mapping
        track_owner_dict = {
            track_id: owner_id
            for (track_id, owner_id) in track_owners_query
        }
        # Generate list of owner ids
        track_owner_list = [
            owner_id for (track_id, owner_id) in track_owners_query
        ]

        # build dict of owner_id --> follower_count
        follower_counts = (session.query(
            Follow.followee_user_id,
            func.count(Follow.followee_user_id)).filter(
                Follow.is_current == True, Follow.is_delete == False,
                Follow.followee_user_id.in_(track_owner_list)).group_by(
                    Follow.followee_user_id).all())
        follower_count_dict = \
                {user_id: follower_count for (user_id, follower_count) in follower_counts}

        save_counts = get_save_counts(session, False, True,
                                      not_deleted_track_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
        }

        trending = []
        for track_entry in listen_counts:
            # Skip over deleted tracks
            if (track_entry[response_name_constants.track_id]
                    not in not_deleted_track_ids):
                continue

            # Populate repost counts
            if track_entry[
                    response_name_constants.track_id] in track_repost_counts:
                track_entry[response_name_constants.repost_count] = \
                        track_repost_counts[track_entry[response_name_constants.track_id]]
            else:
                track_entry[response_name_constants.repost_count] = 0

            # Populate save counts
            if track_entry[
                    response_name_constants.track_id] in track_save_counts:
                track_entry[response_name_constants.save_count] = \
                        track_save_counts[track_entry[response_name_constants.track_id]]
            else:
                track_entry[response_name_constants.save_count] = 0

            # Populate listen counts
            owner_id = track_owner_dict[track_entry[
                response_name_constants.track_id]]
            owner_follow_count = 0
            if owner_id in follower_count_dict:
                owner_follow_count = follower_count_dict[owner_id]
            track_entry[response_name_constants.track_owner_id] = owner_id
            track_entry[response_name_constants.
                        track_owner_follower_count] = owner_follow_count

            trending.append(track_entry)

    final_resp = {}
    final_resp['listen_counts'] = trending
    return api_helpers.success_response(final_resp)
Пример #20
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
Пример #21
0
def search_tags():
    search_str = request.args.get("query", type=str)
    current_user_id = get_current_user_id(required=False)
    if not search_str:
        raise exceptions.ArgumentError("Invalid value for parameter 'query'")

    user_tag_count = request.args.get("user_tag_count", type=str)
    if not user_tag_count:
        user_tag_count = "2"

    kind = request.args.get("kind", type=str, default="all")
    validSearchKinds = [SearchKind.all, SearchKind.tracks, SearchKind.users]
    try:
        searchKind = SearchKind[kind]
        if searchKind not in validSearchKinds:
            raise Exception
    except Exception:
        return api_helpers.error_response(
            "Invalid value for parameter 'kind' must be in %s" %
            [k.name for k in validSearchKinds], 400)

    results = {}

    (limit, offset) = get_pagination_vars()
    db = get_db_read_replica()
    with db.scoped_session() as session:
        if (searchKind in [SearchKind.all, SearchKind.tracks]):
            results['tracks'] = search_track_tags(
                session, {
                    'search_str': search_str,
                    'current_user_id': current_user_id,
                    'limit': limit,
                    'offset': offset
                })

        if (searchKind in [SearchKind.all, SearchKind.users]):
            results['users'] = search_user_tags(
                session, {
                    'search_str': search_str,
                    'current_user_id': current_user_id,
                    "user_tag_count": user_tag_count,
                    'limit': limit,
                    'offset': offset
                })

    # Add personalized results for a given user
    if current_user_id:
        if (searchKind in [SearchKind.all, SearchKind.tracks]):
            # Query saved tracks for the current user that contain this tag
            track_ids = [track['track_id'] for track in results['tracks']]
            track_play_counts = {
                track['track_id']: track[response_name_constants.play_count]
                for track in results['tracks']
            }

            saves_query = (session.query(Save.save_item_id).filter(
                Save.is_current == True, Save.is_delete == False,
                Save.save_type == SaveType.track,
                Save.user_id == current_user_id,
                Save.save_item_id.in_(track_ids)).all())
            saved_track_ids = [i[0] for i in saves_query]
            saved_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_(saved_track_ids),
            ).all())
            saved_tracks = helpers.query_result_to_list(saved_tracks)
            for saved_track in saved_tracks:
                saved_track_id = saved_track["track_id"]
                saved_track[response_name_constants.play_count] = \
                    track_play_counts.get(saved_track_id, 0)
            saved_tracks = \
                populate_track_metadata(
                    session, saved_track_ids, saved_tracks, current_user_id)

            # Sort and paginate
            play_count_sorted_saved_tracks = \
                sorted(
                    saved_tracks, key=lambda i: i[response_name_constants.play_count], reverse=True)

            play_count_sorted_saved_tracks = \
                play_count_sorted_saved_tracks[slice(
                    offset, offset + limit, 1)]

            results['saved_tracks'] = play_count_sorted_saved_tracks

        if (searchKind in [SearchKind.all, SearchKind.users]):
            # Query followed users that have referenced this tag
            user_ids = [user['user_id'] for user in results['users']]
            followed_user_query = (session.query(
                Follow.followee_user_id).filter(
                    Follow.is_current == True, Follow.is_delete == False,
                    Follow.follower_user_id == current_user_id,
                    Follow.followee_user_id.in_(user_ids)).all())
            followed_user_ids = [i[0] for i in followed_user_query]
            followed_users = get_unpopulated_users(session, followed_user_ids)
            followed_users = \
                populate_user_metadata(
                    session,
                    followed_user_ids,
                    followed_users,
                    current_user_id
                )

            followed_users_followee_sorted = \
                sorted(
                    followed_users,
                    key=lambda i: i[response_name_constants.follower_count],
                    reverse=True)

            followed_users_followee_sorted = \
                followed_users_followee_sorted[slice(
                    offset, offset + limit, 1)]

            results['followed_users'] = followed_users_followee_sorted

    return api_helpers.success_response(results)
Пример #22
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 search_tags():
    search_str = request.args.get("query", type=str)
    current_user_id = get_current_user_id(required=False)
    if not search_str:
        raise exceptions.ArgumentError("Invalid value for parameter 'query'")

    user_tag_count = request.args.get("user_tag_count", type=str)
    if not user_tag_count:
        user_tag_count = "2"

    kind = request.args.get("kind", type=str, default="all")
    validSearchKinds = [SearchKind.all, SearchKind.tracks, SearchKind.users]
    try:
        searchKind = SearchKind[kind]
        if searchKind not in validSearchKinds:
            raise Exception
    except Exception:
        return api_helpers.error_response(
            f"Invalid value for parameter 'kind' must be in {[k.name for k in validSearchKinds]}",
            400,
        )

    results = {}

    (limit, offset) = get_pagination_vars()
    db = get_db_read_replica()
    with db.scoped_session() as session:
        if searchKind in [SearchKind.all, SearchKind.tracks]:
            results["tracks"] = search_track_tags(
                session,
                {
                    "search_str": search_str,
                    "current_user_id": current_user_id,
                    "limit": limit,
                    "offset": offset,
                },
            )

        if searchKind in [SearchKind.all, SearchKind.users]:
            results["users"] = search_user_tags(
                session,
                {
                    "search_str": search_str,
                    "current_user_id": current_user_id,
                    "user_tag_count": user_tag_count,
                    "limit": limit,
                    "offset": offset,
                },
            )

    # Add personalized results for a given user
    if current_user_id:
        if searchKind in [SearchKind.all, SearchKind.tracks]:
            # Query saved tracks for the current user that contain this tag
            track_ids = [track["track_id"] for track in results["tracks"]]

            saves_query = (
                session.query(Save.save_item_id)
                .filter(
                    Save.is_current == True,
                    Save.is_delete == False,
                    Save.save_type == SaveType.track,
                    Save.user_id == current_user_id,
                    Save.save_item_id.in_(track_ids),
                )
                .all()
            )
            saved_track_ids = {i[0] for i in saves_query}
            saved_tracks = list(
                filter(
                    lambda track: track["track_id"] in saved_track_ids,
                    results["tracks"],
                )
            )
            results["saved_tracks"] = saved_tracks

        if searchKind in [SearchKind.all, SearchKind.users]:
            # Query followed users that have referenced this tag
            user_ids = [user["user_id"] for user in results["users"]]
            followed_user_query = (
                session.query(Follow.followee_user_id)
                .filter(
                    Follow.is_current == True,
                    Follow.is_delete == False,
                    Follow.follower_user_id == current_user_id,
                    Follow.followee_user_id.in_(user_ids),
                )
                .all()
            )
            followed_user_ids = {i[0] for i in followed_user_query}
            followed_users = list(
                filter(
                    lambda user: user["user_id"] in followed_user_ids, results["users"]
                )
            )
            results["followed_users"] = followed_users

    return api_helpers.success_response(results)
Пример #24
0
def get_top_users(current_user_id):
    """Gets the top users by follows of all of Audius"""
    (limit, offset) = get_pagination_vars()
    db = get_db_read_replica()
    with db.scoped_session() as session:
        return _get_top_users(session, current_user_id, limit, offset)