예제 #1
0
def _get_entity_stats(user_name: str, entity: str, count_key: str):
    user, stats_range = _validate_stats_user_params(user_name)

    offset = get_non_negative_param("offset", default=0)
    count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_stats(user["id"], stats_range, entity)
    if stats is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(
        stats, offset, count)

    return jsonify({
        "payload": {
            "user_id": user_name,
            entity: entity_list,
            "count": len(entity_list),
            count_key: total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": stats.from_ts,
            "to_ts": stats.to_ts,
            "last_updated": int(stats.last_updated.timestamp()),
        }
    })
예제 #2
0
def get_playlists_created_for_user(playlist_user_name):
    """
    Fetch playlist metadata in JSPF format without recordings that have been created for the user.
    Createdfor playlists are all public, so no Authorization is needed for this call.

    :params count: The number of playlists to return (for pagination). Default
        :data:`~webserver.views.api.DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL`
    :params offset: The offset of into the list of playlists to return (for pagination)
    :statuscode 200: Yay, you have data!
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """

    count = get_non_negative_param('count',
                                   DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL)
    offset = get_non_negative_param('offset', 0)
    playlist_user = db_user.get_by_mb_id(playlist_user_name)
    if playlist_user is None:
        raise APINotFound("Cannot find user: %s" % playlist_user_name)

    playlists, playlist_count = db_playlist.get_playlists_created_for_user(
        playlist_user["id"], load_recordings=False, count=count, offset=offset)

    return jsonify(
        serialize_playlists(playlists, playlist_count, count, offset))
예제 #3
0
def _get_sitewide_stats(entity: str):
    stats_range = request.args.get("range", default="all_time")
    if not _is_valid_range(stats_range):
        raise APIBadRequest(f"Invalid range: {stats_range}")

    offset = get_non_negative_param("offset", default=0)
    count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_sitewide_stats(stats_range, entity)
    if stats is None:
        raise APINoContent("")

    entity_list, total_entity_count = _process_user_entity(
        stats, offset, count)
    return jsonify({
        "payload": {
            entity: entity_list,
            "range": stats_range,
            "offset": offset,
            "count": total_entity_count,
            "from_ts": stats.from_ts,
            "to_ts": stats.to_ts,
            "last_updated": int(stats.last_updated.timestamp())
        }
    })
예제 #4
0
def _get_feedback_for_recording(recording_type, recording):
    score = _parse_int_arg('score')

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    if score:
        if score not in [-1, 1]:
            log_raise_400("Score can have a value of 1 or -1.", request.args)

    feedback = db_feedback.get_feedback_for_recording(recording_type,
                                                      recording,
                                                      limit=count,
                                                      offset=offset,
                                                      score=score)
    total_count = db_feedback.get_feedback_count_for_recording(
        recording_type, recording)

    feedback = [fb.to_api() for fb in feedback]

    return jsonify({
        "feedback": feedback,
        "count": len(feedback),
        "total_count": total_count,
        "offset": offset
    })
예제 #5
0
def get_playlists_for_user(playlist_user_name):
    """
    Fetch playlist metadata in JSPF format without recordings for the given user.
    If a user token is provided in the Authorization header, return private playlists as well
    as public playlists for that user.

    :params count: The number of playlists to return (for pagination). Default
        :data:`~webserver.views.api.DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL`
    :params offset: The offset of into the list of playlists to return (for pagination)
    :statuscode 200: Yay, you have data!
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = validate_auth_header(True)

    count = get_non_negative_param('count',
                                   DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL)
    offset = get_non_negative_param('offset', 0)
    playlist_user = db_user.get_by_mb_id(playlist_user_name)
    if playlist_user is None:
        raise APINotFound("Cannot find user: %s" % playlist_user_name)

    include_private = True if user and user["id"] == playlist_user[
        "id"] else False
    playlists, playlist_count = db_playlist.get_playlists_for_user(
        playlist_user["id"],
        include_private=include_private,
        load_recordings=False,
        count=count,
        offset=offset)

    return jsonify(
        serialize_playlists(playlists, playlist_count, count, offset))
예제 #6
0
def get_playlists_collaborated_on_for_user(playlist_user_name):
    """
    Fetch playlist metadata in JSPF format without recordings for which a user is a collaborator.
    If a playlist is private, it will only be returned if the caller is authorized to edit that playlist.

    :params count: The number of playlists to return (for pagination). Default
        :data:`~webserver.views.api.DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL`
    :params offset: The offset of into the list of playlists to return (for pagination)
    :statuscode 200: Yay, you have data!
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """

    user = validate_auth_header(True)

    count = get_non_negative_param('count',
                                   DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL)
    offset = get_non_negative_param('offset', 0)
    playlist_user = db_user.get_by_mb_id(playlist_user_name)
    if playlist_user is None:
        raise APINotFound("Cannot find user: %s" % playlist_user_name)

    # TODO: This needs to be passed to the DB layer
    include_private = True if user and user["id"] == playlist_user[
        "id"] else False
    playlists, playlist_count = db_playlist.get_playlists_collaborated_on(
        playlist_user["id"],
        include_private=include_private,
        load_recordings=False,
        count=count,
        offset=offset)

    return jsonify(
        serialize_playlists(playlists, playlist_count, count, offset))
예제 #7
0
def get_feedback_for_user(user_name):
    """
    Get feedback given by user ``user_name``. The format for the JSON returned is defined in our :ref:`feedback-json-doc`.

    If the optional argument ``score`` is not given, this endpoint will return all the feedback submitted by the user.
    Otherwise filters the feedback to be returned by score.

    :param score: Optional, If 1 then returns the loved recordings, if -1 returns hated recordings.
    :type score: ``int``
    :param count: Optional, number of feedback items to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`.
    :type count: ``int``
    :param offset: Optional, number of feedback items to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 feedback will be skipped, defaults to 0.
    :type offset: ``int``
    :param metadata: Optional, 'true' or 'false' if this call should return the metadata for the feedback.
    :type metadata: ``str``
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*
    """

    score = _parse_int_arg('score')
    metadata = parse_boolean_arg('metadata')

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    if score:
        if score not in [-1, 1]:
            log_raise_400("Score can have a value of 1 or -1.", request.args)

    feedback = db_feedback.get_feedback_for_user(user_id=user["id"],
                                                 limit=count,
                                                 offset=offset,
                                                 score=score,
                                                 metadata=metadata)
    total_count = db_feedback.get_feedback_count_for_user(user["id"])

    feedback = [fb.to_api() for fb in feedback]

    return jsonify({
        "feedback": feedback,
        "count": len(feedback),
        "total_count": total_count,
        "offset": offset
    })
def get_feedback_for_recording(recording_msid):
    """
    Get feedback for recording with given ``recording_msid``. The format for the JSON returned
    is defined in our :ref:`feedback-json-doc`.

    :param score: Optional, If 1 then returns the loved recordings, if -1 returns hated recordings.
    :type score: ``int``
    :param count: Optional, number of feedback items to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`.
    :type count: ``int``
    :param offset: Optional, number of feedback items to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 feedback will be skipped, defaults to 0.
    :type offset: ``int``
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*
    """

    if not is_valid_uuid(recording_msid):
        log_raise_400("%s MSID format invalid." % recording_msid)

    score = _parse_int_arg('score')

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    if score:
        if score not in [-1, 1]:
            log_raise_400("Score can have a value of 1 or -1.", request.args)

    feedback = db_feedback.get_feedback_for_recording(
        recording_msid=recording_msid, limit=count, offset=offset, score=score)
    total_count = db_feedback.get_feedback_count_for_recording(recording_msid)

    feedback = [_feedback_to_api(fb) for fb in feedback]

    return jsonify({
        "feedback": feedback,
        "count": len(feedback),
        "total_count": total_count,
        "offset": offset
    })
예제 #9
0
def get_sitewide_artist():
    """
    Get sitewide top artists.


    A sample response from the endpoint may look like::

        {
            "payload": {
                "time_ranges": [
                    {
                        "time_range": "April 2020",
                        "artists": [
                            {
                                "artist_mbids": ["f4fdbb4c-e4b7-47a0-b83b-d91bbfcfa387"],
                                "artist_msid": "b4ae3356-b8a7-471a-a23a-e471a69ad454",
                                "artist_name": "Ariana Grande",
                                "listen_count": 519
                            },
                            {
                                "artist_mbids": ["f4abc0b5-3f7a-4eff-8f78-ac078dbce533"],
                                "artist_msid": "f9ee09fb-5ab4-46a2-9088-3eac0eed4920",
                                "artist_name": "Billie Eilish",
                                "listen_count": 447
                            }
                        ],
                        "from_ts": 1585699200,
                        "to_ts": 1588291199,
                    },
                    {
                        "time_range": "May 2020",
                        "artists": [
                            {
                                "artist_mbids": [],
                                "artist_msid": "2b0646af-f3f0-4a5b-b629-6c31301c1c29",
                                "artist_name": "The Weeknd",
                                "listen_count": 621
                            },
                            {
                                "artist_mbids": [],
                                "artist_msid": "9720fd77-fe48-41ba-a7a2-b4795718dd97",
                                "artist_name": "Drake",
                                "listen_count": 554
                            }
                        ],
                        "from_ts": 1588291200,
                        "to_ts": 1590969599
                    }
                ],
                "offset": 0,
                "count": 2,
                "range": "year",
                "last_updated": 1588494361,
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - ``artist_mbids`` and ``artist_msid`` are optional fields and may not be present in all the entries
        - The example above shows the data for two days only, however we calculate the statistics for
          the current time range and the previous time range. For example for yearly statistics the data
          is calculated for the months in current as well as the past year.
        - We only calculate the top 1000 artists for each time period.

    :param count: Optional, number of artists to return for each time range,
        Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of artists to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 artists will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :resheader Content-Type: *application/json*
    """
    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_sitewide_artists(stats_range)
    if stats is None or stats.data is None:
        raise APINoContent('')

    entity_data = _get_sitewide_entity_list(stats.data,
                                            entity="artists",
                                            offset=offset,
                                            count=count)
    return jsonify({
        'payload': {
            "time_ranges": entity_data,
            "range": stats_range,
            "offset": offset,
            "count": min(count, MAX_ITEMS_PER_GET),
            "from_ts": stats.data.from_ts,
            "to_ts": stats.data.to_ts,
            "last_updated": int(stats.last_updated.timestamp())
        }
    })
예제 #10
0
def get_recording(user_name):
    """
    Get top recordings for user ``user_name``.


    A sample response from the endpoint may look like::

        {
            "payload": {
                "recordings": [
                    {
                        "artist_mbids": [],
                        "artist_msid": "7addbcac-ae39-4b4c-a956-53da336d68e8",
                        "artist_name": "Ellie Goulding",
                        "listen_count": 25,
                        "recording_mbid": "0fe11cd3-0be4-467b-84fa-0bd524d45d74",
                        "recording_msid": "c6b65a7e-7284-433e-ac5d-e3ff0aa4738a",
                        "release_mbid": "",
                        "release_msid": "de97ca87-36c4-4995-a5c9-540e35944352",
                        "release_name": "Delirium (Deluxe)",
                        "track_name": "Love Me Like You Do - From \"Fifty Shades of Grey\""
                    },
                    {
                        "artist_mbids": [],
                        "artist_msid": "3b155259-b29e-4515-aa62-cb0b917f4cfd",
                        "artist_name": "The Fray",
                        "listen_count": 23,
                        "recording_mbid": "0008ab49-a6ad-40b5-aa90-9d2779265c22",
                        "recording_msid": "4b5bf07c-782f-4324-9242-bf56e4ba1e57",
                        "release_mbid": "",
                        "release_msid": "2b2a93c3-a0bd-4f46-8507-baf5ad291966",
                        "release_name": "How to Save a Life",
                        "track_name": "How to Save a Life"
                    },
                ],
                "count": 2,
                "total_recording_count": 175,
                "range": "all_time",
                "last_updated": 1588494361,
                "user_id": "John Doe",
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - We only calculate the top 1000 all_time recordings
        - ``artist_mbids``, ``artist_msid``, ``release_name``, ``release_mbid``, ``release_msid``,
          ``recording_mbid`` and ``recording_msid`` are optional fields and may not be present in all the responses

    :param count: Optional, number of recordings to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of recordings to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 recordings will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_recordings(user['id'], stats_range)
    if stats is None or getattr(stats, stats_range) is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(stats,
                                                           stats_range,
                                                           offset,
                                                           count,
                                                           entity='recording')
    from_ts = int(getattr(stats, stats_range).from_ts)
    to_ts = int(getattr(stats, stats_range).to_ts)
    last_updated = int(stats.last_updated.timestamp())

    return jsonify({
        'payload': {
            "user_id": user_name,
            'recordings': entity_list,
            "count": len(entity_list),
            "total_recording_count": total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": from_ts,
            "to_ts": to_ts,
            "last_updated": last_updated,
        }
    })
예제 #11
0
def get_user_artist(user_name):
    """
    Get top artists for user ``user_name``.


    A sample response from the endpoint may look like::

        {
            "payload": {
                "artists": [
                    {
                       "artist_mbids": ["93e6118e-7fa8-49f6-9e02-699a1ebce105"],
                       "artist_msid": "d340853d-7408-4a0d-89c2-6ff13e568815",
                       "artist_name": "The Local train",
                       "listen_count": 385
                    },
                    {
                       "artist_mbids": ["ae9ed5e2-4caf-4b3d-9cb3-2ad626b91714"],
                       "artist_msid": "ba64b195-01dd-4613-9534-bb87dc44cffb",
                       "artist_name": "Lenka",
                       "listen_count": 333
                    },
                    {
                       "artist_mbids": ["cc197bad-dc9c-440d-a5b5-d52ba2e14234"],
                       "artist_msid": "6599e41e-390c-4855-a2ac-68ee798538b4",
                       "artist_name": "Coldplay",
                       "listen_count": 321
                    }
                ],
                "count": 3,
                "total_artist_count": 175,
                "range": "all_time",
                "last_updated": 1588494361,
                "user_id": "John Doe",
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - ``artist_mbids`` and ``artist_msid`` are optional fields and may not be present in all the responses

    :param count: Optional, number of artists to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of artists to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 artists will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_artists(user['id'], stats_range)
    if stats is None or getattr(stats, stats_range) is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(stats,
                                                           stats_range,
                                                           offset,
                                                           count,
                                                           entity='artist')
    from_ts = int(getattr(stats, stats_range).from_ts)
    to_ts = int(getattr(stats, stats_range).to_ts)
    last_updated = int(stats.last_updated.timestamp())

    return jsonify({
        'payload': {
            "user_id": user_name,
            "artists": entity_list,
            "count": len(entity_list),
            "total_artist_count": total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": from_ts,
            "to_ts": to_ts,
            "last_updated": last_updated,
        }
    })
def get_pins_for_user_following(user_name):
    """
    Get a list containing the active pinned recordings for all users in a user's ``user_name``
    following list. The returned pinned recordings are sorted in descending order of the time they were pinned.
    The JSON returned by the API will look like the following:

    .. code-block:: json

        {
            "count": 1,
            "offset": 0,
            "pinned_recordings": [
                {
                "blurb_content": "Spectacular recording!",
                "created": 1624000841,
                "row_id": 1,
                "pinned_until": 1624605641,
                "recording_mbid": null,
                "recording_msid": "40ef0ae1-5626-43eb-838f-1b34187519bf",
                "track_metadata": {
                        "artist_msid": "8c7b4641-e363-4598-ae70-7709840fb934",
                        "artist_name": "Rick Astley",
                        "track_name": "Never Gonna Give You Up"
                },
                "user_name": "-- the MusicBrainz ID of the user who pinned this recording --"
                },
                "-- more pinned recordings from different users here ---"
            ],
            "user_name": "-- the MusicBrainz ID of the original user --"
        }

    :param user_name: the MusicBrainz ID of the user whose followed user's pinned recordings are being requested.
    :type user_name: ``str``
    :param count: Optional, number of pinned recording items to return,
        Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of pinned recording items to skip from the beginning, for pagination.
        Ex. An offset of 5 means the most recent pinned recordings from the first 5 users will be skipped, defaults to 0
    :type offset: ``int``
    :statuscode 200: Yay, you have data!
    :statuscode 400: Invalid query parameters. See error message for details.
    :statuscode 404: The requested user was not found.
    :resheader Content-Type: *application/json*
    """

    count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET)
    offset = get_non_negative_param("offset", default=0)

    count = min(count, MAX_ITEMS_PER_GET)

    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    pinned_recordings = db_pinned_rec.get_pins_for_user_following(
        user_id=user["id"], count=count, offset=offset)
    pinned_recordings = fetch_track_metadata_for_pins(pinned_recordings)
    pinned_recordings = [
        _pinned_recording_to_api(pin) for pin in pinned_recordings
    ]

    return jsonify({
        "pinned_recordings": pinned_recordings,
        "count": len(pinned_recordings),
        "offset": offset,
        "user_name": user_name,
    })
예제 #13
0
def get_release(user_name):
    """
    Get top releases for user ``user_name``.


    A sample response from the endpoint may look like::

        {
            "payload": {
                "releases": [
                    {
                        "artist_mbids": [],
                        "artist_msid": "6599e41e-390c-4855-a2ac-68ee798538b4",
                        "artist_name": "Coldplay",
                        "listen_count": 26,
                        "release_mbid": "",
                        "release_msid": "d59730cf-f0e3-441e-a7a7-8e0f589632a5",
                        "release_name": "Live in Buenos Aires"
                    },
                    {
                        "artist_mbids": [],
                        "artist_msid": "7addbcac-ae39-4b4c-a956-53da336d68e8",
                        "artist_name": "Ellie Goulding",
                        "listen_count": 25,
                        "release_mbid": "",
                        "release_msid": "de97ca87-36c4-4995-a5c9-540e35944352",
                        "release_name": "Delirium (Deluxe)"
                    },
                    {
                        "artist_mbids": [],
                        "artist_msid": "3b155259-b29e-4515-aa62-cb0b917f4cfd",
                        "artist_name": "The Fray",
                        "listen_count": 25,
                        "release_mbid": "",
                        "release_msid": "2b2a93c3-a0bd-4f46-8507-baf5ad291966",
                        "release_name": "How to Save a Life"
                    },
                ],
                "count": 3,
                "total_release_count": 175,
                "range": "all_time",
                "last_updated": 1588494361,
                "user_id": "John Doe",
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - ``artist_mbids``, ``artist_msid``, ``release_mbid`` and ``release_msid`` are optional fields and
          may not be present in all the responses

    :param count: Optional, number of releases to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of releases to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 releases will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_releases(user['id'], stats_range)
    if stats is None or getattr(stats, stats_range) is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(stats,
                                                           stats_range,
                                                           offset,
                                                           count,
                                                           entity='release')
    from_ts = int(getattr(stats, stats_range).from_ts)
    to_ts = int(getattr(stats, stats_range).to_ts)
    last_updated = int(stats.last_updated.timestamp())

    return jsonify({
        'payload': {
            "user_id": user_name,
            'releases': entity_list,
            "count": len(entity_list),
            "total_release_count": total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": from_ts,
            "to_ts": to_ts,
            "last_updated": last_updated,
        }
    })
def get_pins_for_user(user_name):
    """
    Get a list of all recordings ever pinned by a user with given ``user_name`` in descending order of the time
    they were originally pinned. The JSON returned by the API will look like the following:

    .. code-block:: json

        {
            "count": 10,
            "offset": 0,
            "pinned_recordings": [
                {
                    "blurb_content": "Awesome recording!",
                    "created": 1623997168,
                    "row_id": 10,
                    "pinned_until": 1623997485,
                    "recording_mbid": null,
                    "recording_msid": "fd7d9162-a284-4a10-906c-faae4f1e166b"
                    "track_metadata": {
                        "artist_msid": "8c7b4641-e363-4598-ae70-7709840fb934",
                        "artist_name": "Rick Astley",
                        "track_name": "Never Gonna Give You Up"
                        }
                },
                "-- more pinned recording items here ---"
            ],
            "total_count": 10,
            "user_name": "-- the MusicBrainz ID of the user --"
        }

    :param user_name: the MusicBrainz ID of the user whose pin track history requested.
    :type user_name: ``str``
    :param count: Optional, number of pinned recording items to return,
        Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of pinned recording items to skip from the beginning, for pagination.
        Ex. An offset of 5 means the most recent 5 pinned recordings from the user will be skipped, defaults to 0
    :type offset: ``int``
    :statuscode 200: Yay, you have data!
    :statuscode 400: Invalid query parameters. See error message for details.
    :statuscode 404: The requested user was not found.
    :resheader Content-Type: *application/json*
    """
    offset = get_non_negative_param("offset", default=0)
    count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    try:
        pinned_recordings = db_pinned_rec.get_pin_history_for_user(
            user_id=user["id"], count=count, offset=offset)
    except Exception as e:
        current_app.logger.error(
            "Error while retrieving pins for user: {}".format(e))
        raise APIInternalServerError("Something went wrong. Please try again.")

    pinned_recordings = fetch_track_metadata_for_pins(pinned_recordings)
    pinned_recordings = [
        _pinned_recording_to_api(pin) for pin in pinned_recordings
    ]
    total_count = db_pinned_rec.get_pin_count_for_user(user_id=user["id"])

    return jsonify({
        "pinned_recordings": pinned_recordings,
        "total_count": total_count,
        "count": len(pinned_recordings),
        "offset": offset,
        "user_name": user_name,
    })
예제 #15
0
def get_missing_musicbrainz_data(user_name):
    """ Get musicbrainz data sorted on "listened_at" that the user has submitted to ListenBrainz but has not
        submitted to MusicBrainz.

        A sample response from the endpoint may look like:

        .. code-block:: json

            {
                "payload":
                {
                    "last_updated": 1588494361,
                    "data": [
                        {
                            "artist_msid": "fd32e967-b874-44b2-809c-3862f714813c",
                            "artist_name": "Red City Radio",
                            "listened_at": "2020-04-29 23:40:47",
                            "recording_msid": "78f63ece-86e1-48bf-a7ff-29793d4a84e6",
                            "release_msid": "47818692-f669-4846-acbc-cb0a69987aee",
                            "release_name": "The Dangers Of Standing Still",
                            "track_name": "Never Bring A Cup Of Water To A Gunfight"
                        },
                        {
                            "artist_msid": "fd32e967-b874-44b2-809c-3862f714813c",
                            "artist_name": "Red City Radio",
                            "listened_at": "2020-04-29 23:37:57",
                            "recording_msid": "d226200a-a9be-4e9e-9f7c-d74a71647893",
                            "release_msid": "47818692-f669-4846-acbc-cb0a69987aee",
                            "release_name": "The Dangers Of Standing Still",
                            "track_name": "Nathaniel Martinez"
                        }
                    ],
                    "count": 2,
                    "offset": 4,
                    "total_data_count": 25,
                    "user_name": "Vansika"
                }
            }

        :param count: Optional, number of records to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
            Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
        :type count: ``int``

        :param offset: Optional, number of records to skip from the beginning, for pagination.
            Ex. An offset of 5 means the 5 records will be skipped, defaults to 0
        :type offset: ``int``

        :statuscode 200: Successful query, you have data!
        :statuscode 400: Bad request, check ``response['error']`` for more details
        :statuscode 404: User not found.
        :statuscode 204: Missing MusicBrainz data for the user not calculated , empty response will be returned
    """
    # source indicates the *source* script/algorithm by which the missing musicbrainz data was calculated.
    # The source may change in future
    source = 'cf'

    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: {}".format(user_name))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    data = db_missing_musicbrainz_data.get_user_missing_musicbrainz_data(
        user['id'], source)

    if data is None:
        err_msg = 'Missing MusicBrainz data for {} not calculated'.format(
            user_name)
        raise APINoContent(err_msg)

    missing_musicbrainz_data_list = getattr(
        data, 'data').dict()['missing_musicbrainz_data']

    missing_musicbrainz_data_list_filtered = missing_musicbrainz_data_list[
        offset:count]

    for index in range(len(missing_musicbrainz_data_list_filtered)):
        missing_musicbrainz_data_list_filtered[index]["listened_at"] += "Z"

    payload = {
        'payload': {
            'user_name': user_name,
            'last_updated': int(getattr(data, 'created').timestamp()),
            'count': len(missing_musicbrainz_data_list_filtered),
            'total_data_count': len(missing_musicbrainz_data_list),
            'offset': offset,
            'data': missing_musicbrainz_data_list_filtered
        }
    }

    return jsonify(payload)
예제 #16
0
def get_feedback_for_user(user_name):
    """
    Get feedback given by user ``user_name``.

    A sample response may look like:

    .. code-block:: json

        {
            "count": 1,
            "feedback": [
                {
                    "created": "1345679998",
                    "recording_mbid": "d23f4719-9212-49f0-ad08-ddbfbfc50d6f",
                    "rating": "love"
                },
                "-- more feedback data here ---"
            ],
            "offset": 0,
            "total_count": 1,
            "user_name": "Vansika"
        }

    If the optional argument ``rating`` is not given, this endpoint will return all the feedback submitted by the user.
    Otherwise filters the feedback to be returned by rating.

    :param rating: Optional, refer to db/model/recommendation_feedback.py for allowed rating values.
    :type rating: ``str``
    :param count: Optional, number of feedback items to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`.
    :type count: ``int``
    :param offset: Optional, number of feedback items to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 feedback will be skipped, defaults to 0.
    :type offset: ``int``
    :statuscode 200: Yay, you have data!
    :statuscode 404: User not found.
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :resheader Content-Type: *application/json*
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: {}".format(user_name))

    rating = request.args.get('rating')

    if rating:
        expected_rating = get_allowed_ratings()
        if rating not in expected_rating:
            log_raise_400("Rating must be in {}".format(expected_rating), request.args)

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    count = min(count, MAX_ITEMS_PER_GET)

    feedback = db_feedback.get_feedback_for_user(user_id=user["id"], limit=count, offset=offset, rating=rating)
    total_count = db_feedback.get_feedback_count_for_user(user["id"])

    feedback = [_format_feedback(fb) for fb in feedback]

    return jsonify({
        "feedback": feedback,
        "count": len(feedback),
        "total_count": total_count,
        "offset": offset,
        "user_name": user["musicbrainz_id"]
    })
def get_recommendations(user_name):
    """ Get recommendations sorted on rating and ratings for user ``user_name``.

        A sample response from the endpoint may look like:

        .. code-block:: json

            {
                "payload": {
                    "last_updated": 1588494361,
                    "type": "<artist_type>",
                    "entity": "recording",
                    "mbids": [
                        {
                            "recording_mbid": "526bd613-fddd-4bd6-9137-ab709ac74cab",
                            "score": 9.345
                        },
                        {
                            "recording_mbid": "a6081bc1-2a76-4984-b21f-38bc3dcca3a5",
                            "score": 6.998
                        }
                    ],
                    "user_name": "unclejohn69",
                    "count": 10,
                    "total_mbid_count": 30,
                    "offset": 10
                }
            }


        .. note::
            - This endpoint is experimental and probably will change in the future.
            - <artist_type>: 'top' or 'similar'

        :param artist_type: Mandatory, artist type in ['top', 'similar']

            Ex. artist_type = top will fetch recommended recording mbids that belong to top artists listened to by the user.

            artist_type = similar will fetch recommended recording mbids that belong to artists similar to top artists listened to by the user.
        :type artist_type: ``str``

        :param count: Optional, number of recording mbids to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
            Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
        :type count: ``int``

        :param offset: Optional, number of mbids to skip from the beginning, for pagination.
            Ex. An offset of 5 means the 5 mbids will be skipped, defaults to 0
        :type offset: ``int``

        :statuscode 200: Successful query, you have data!
        :statuscode 400: Bad request, check ``response['error']`` for more details
        :statuscode 404: User not found.
        :statuscode 204: Recommendations for the user haven't been generated, empty response will be returned
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: {}".format(user_name))

    artist_type = request.args.get('artist_type')
    if not _is_valid_artist_type(artist_type):
        raise APIBadRequest("Invalid artist type: {}".format(artist_type))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    recommendations = db_recommendations_cf_recording.get_user_recommendation(user['id'])

    if recommendations is None:
        err_msg = 'No recommendations due to absence of recent listening history for user {}'.format(user_name)
        raise APINoContent(err_msg)

    mbid_list, total_mbid_count = _process_recommendations(recommendations, count, artist_type, user_name, offset)

    payload = {
        'payload': {
            'mbids': mbid_list,
            'entity': "recording",
            'type': artist_type,
            'user_name': user_name,
            'last_updated': int(getattr(recommendations, 'created').timestamp()),
            'count': len(mbid_list),
            'total_mbid_count': total_mbid_count,
            'offset': offset
        }
    }

    return jsonify(payload)
예제 #18
0
def get_sitewide_artist():
    """
    Get sitewide top artists.


    A sample response from the endpoint may look like:

    .. code-block:: json

        {
            "payload": {
                "artists": [
                    {
                        "artist_mbids": [],
                        "artist_name": "Kanye West",
                        "listen_count": 1305
                    },
                    {
                        "artist_mbids": ["0b30341b-b59d-4979-8130-b66c0e475321"],
                        "artist_name": "Lil Nas X",
                        "listen_count": 1267
                    }
                ],
                "offset": 0,
                "count": 2,
                "range": "year",
                "last_updated": 1588494361,
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - ``artist_mbids`` and ``artist_msid`` are optional fields and may not be present in all the entries
        - We only calculate the top 1000 artists for each time period.

    :param count: Optional, number of artists to return for each time range,
        Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of artists to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 artists will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :resheader Content-Type: *application/json*
    """
    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_sitewide_stats(stats_range, 'artists')
    if stats is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(
        stats, offset, count)
    return jsonify({
        "payload": {
            "artists": entity_list,
            "range": stats_range,
            "offset": offset,
            "count": total_entity_count,
            "from_ts": stats.from_ts,
            "to_ts": stats.to_ts,
            "last_updated": int(stats.last_updated.timestamp())
        }
    })