示例#1
0
def generate_data(from_date, num_records, user_name):
    test_data = []
    current_date = to_epoch(from_date)
    artist_msid = str(uuid.uuid4())

    user = db_user.get_by_mb_id(user_name)
    if not user:
        db_user.create(user_name)
        user = db_user.get_by_mb_id(user_name)

    for i in range(num_records):
        current_date += 1   # Add one second
        item = Listen(
            user_id=user['id'],
            user_name=user_name,
            timestamp=datetime.utcfromtimestamp(current_date),
            artist_msid=artist_msid,
            recording_msid=str(uuid.uuid4()),
            release_msid=str(uuid.uuid4()),
            data={
                'artist_name': 'Test Artist Pls ignore',
                'track_name': 'Hello Goodbye',
                'additional_info': {},
            },
        )
        test_data.append(item)
    return test_data
    def test_reset_latest_import(self):
        user = db_user.get_or_create(7, 'resetlatestimportuser')
        self.assertEqual(int(user['latest_import'].strftime('%s')), 0)

        val = int(time.time())
        db_user.update_latest_import(user['musicbrainz_id'], val)
        user = db_user.get_by_mb_id(user['musicbrainz_id'])
        self.assertEqual(int(user['latest_import'].strftime('%s')), val)

        db_user.reset_latest_import(user['musicbrainz_id'])
        user = db_user.get_by_mb_id(user['musicbrainz_id'])
        self.assertEqual(int(user['latest_import'].strftime('%s')), 0)
def follow_user(user_name: str):
    """
    Follow the user ``user_name``. A user token (found on  https://listenbrainz.org/profile/ ) must
    be provided in the Authorization header!

    :reqheader Authorization: Token <user token>
    :reqheader Content-Type: *application/json*
    :statuscode 200: Successfully followed the user ``user_name``.
    :statuscode 400:
                    - Already following the user ``user_name``.
                    - Trying to follow yourself.
    :statuscode 401: invalid authorization. See error message for details.
    :resheader Content-Type: *application/json*
    """
    current_user = validate_auth_header()
    user = db_user.get_by_mb_id(user_name)

    if not user:
        raise APINotFound("User %s not found" % user_name)

    if user["musicbrainz_id"] == current_user["musicbrainz_id"]:
        raise APIBadRequest("Whoops, cannot follow yourself.")

    if db_user_relationship.is_following_user(current_user["id"], user["id"]):
        raise APIBadRequest("%s is already following user %s" % (current_user["musicbrainz_id"], user["musicbrainz_id"]))

    try:
        db_user_relationship.insert(current_user["id"], user["id"], "follow")
    except Exception as e:
        current_app.logger.error("Error while trying to insert a relationship: %s", str(e))
        raise APIInternalServerError("Something went wrong, please try again later")

    return jsonify({"status": "ok"})
    def test_increase_latest_import(self):
        user = db_user.get_or_create(4, 'testlatestimportuser')

        val = int(time.time())
        db_user.increase_latest_import(user['musicbrainz_id'], val)
        user = db_user.get_by_mb_id(user['musicbrainz_id'])
        self.assertEqual(val, int(user['latest_import'].strftime('%s')))

        db_user.increase_latest_import(user['musicbrainz_id'], val - 10)
        user = db_user.get_by_mb_id(user['musicbrainz_id'])
        self.assertEqual(val, int(user['latest_import'].strftime('%s')))

        val += 10
        db_user.increase_latest_import(user['musicbrainz_id'], val)
        user = db_user.get_by_mb_id(user['musicbrainz_id'])
        self.assertEqual(val, int(user['latest_import'].strftime('%s')))
示例#5
0
def handle_recommendations(data):
    """ Take recommended recordings for a user and save it in the db.
    """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)
    if not user:
        current_app.logger.critical(
            "Generated recommendations for a user that doesn't exist in the Postgres database: {}"
            .format(musicbrainz_id))
        return

    current_app.logger.debug(
        "inserting recommendation for {}".format(musicbrainz_id))
    recommendations = data['recommendations']

    try:
        db_recommendations_cf_recording.insert_user_recommendation(
            user['id'],
            UserRecommendationsJson(
                **{
                    'top_artist': recommendations['top_artist'],
                    'similar_artist': recommendations['similar_artist']
                }))
    except ValidationError:
        current_app.logger.error(
            """ValidationError while inserting recommendations for user with musicbrainz_id:
                                 {musicbrainz_id}. \nData: {data}""".format(
                musicbrainz_id=musicbrainz_id, data=json.dumps(data,
                                                               indent=3)))

    current_app.logger.debug(
        "recommendation for {} inserted".format(musicbrainz_id))
示例#6
0
def handle_user_daily_activity(data):
    """ Take daily activity stats for user and save it in database. """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)
    if not user:
        current_app.logger.critical(
            "Calculated stats for a user that doesn't exist in the Postgres database: %s",
            musicbrainz_id)
        return

    # send a notification if this is a new batch of stats
    if is_new_user_stats_batch():
        notify_user_stats_update(stat_type=data.get('type', ''))
    current_app.logger.debug("inserting stats for user %s", musicbrainz_id)

    stats_range = data['stats_range']

    # Strip extra data
    to_remove = {'musicbrainz_id', 'type', 'stats_range'}
    data_mod = {key: data[key] for key in data if key not in to_remove}

    try:
        db_stats.insert_user_daily_activity(
            user['id'], UserDailyActivityStatJson(**{stats_range: data_mod}))
    except ValidationError:
        current_app.logger.error(
            """ValidationError while inserting {stats_range} daily_activity for user with
                                    user_id: {user_id}. Data: {data}""".format(
                user_id=user['id'], data=json.dumps(data_mod, indent=3)),
            exc_info=True)
示例#7
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))
示例#8
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))
示例#9
0
def get_following(user_name: str):
    """
    Fetch the list of users followed by the user ``user_name``. Returns a JSON with an array of user names like these:

    .. code-block:: json

        {
            "followers": ["rob", "mr_monkey", "..."],
            "user": "******"
        }

    :statuscode 200: Yay, you have data!
    :statuscode 404: User not found
    """
    user = db_user.get_by_mb_id(user_name)

    if not user:
        raise APINotFound("User %s not found" % user_name)

    try:
        following = db_user_relationship.get_following_for_user(user["id"])
        following = [user["musicbrainz_id"] for user in following]
    except Exception as e:
        current_app.logger.error("Error while trying to fetch following: %s",
                                 str(e))
        raise APIInternalServerError(
            "Something went wrong, please try again later")

    return jsonify({"following": following, "user": user["musicbrainz_id"]})
示例#10
0
def handshake():
    """ View to handshake with the client.

        Creates a new session stored in api_compat.session and then later uses it
        to submit listens. We do auth assuming the LB auth_tokens to be
        passwords, so this should work when users enter their LB auth tokens as passwords
        in clients.
    """

    if request.args.get('hs', '') != 'true':
        return redirect('https://listenbrainz.org/lastfm-proxy')

    user = db_user.get_by_mb_id(request.args.get('u'))
    if user is None:
        return 'BADAUTH\n', 401

    auth_token = request.args.get('a', '')
    timestamp = request.args.get('t', 0)

    correct = _get_audioscrobbler_auth_token(user['auth_token'], timestamp)

    if auth_token != _get_audioscrobbler_auth_token(user['auth_token'],
                                                    timestamp):
        return 'BADAUTH\n', 401

    session = Session.create_by_user_id(user['id'])
    current_app.logger.info('New session created with id: {}'.format(
        session.sid))

    return '\n'.join([
        'OK', session.sid,
        '{}/np_1.2'.format(current_app.config['LASTFM_PROXY_URL']),
        '{}/protocol_1.2\n'.format(current_app.config['LASTFM_PROXY_URL'])
    ])
示例#11
0
def unfollow_user(user_name: str):
    """
    Unfollow the user ``user_name``. A user token (found on  https://listenbrainz.org/profile/ ) must
    be provided in the Authorization header!

    :reqheader Authorization: Token <user token>
    :reqheader Content-Type: *application/json*
    :statuscode 200: Successfully unfollowed the user ``user_name``.
    :statuscode 401: invalid authorization. See error message for details.
    :resheader Content-Type: *application/json*
    """
    current_user = validate_auth_header()
    user = db_user.get_by_mb_id(user_name)

    if not user:
        raise APINotFound("User %s not found" % user_name)

    try:
        db_user_relationship.delete(current_user["id"], user["id"], "follow")
    except Exception as e:
        current_app.logger.error(
            "Error while trying to delete a relationship: %s", str(e))
        raise APIInternalServerError(
            "Something went wrong, please try again later")

    return jsonify({"status": "ok"})
def handshake():
    """ View to handshake with the client.

        Creates a new session stored in api_compat.session and then later uses it
        to submit listens. We do auth assuming the LB auth_tokens to be
        passwords, so this should work when users enter their LB auth tokens as passwords
        in clients.
    """

    if request.args.get('hs', '') != 'true':
        return redirect('https://listenbrainz.org/lastfm-proxy')

    user = db_user.get_by_mb_id(request.args.get('u'))
    if user is None:
        return 'BADAUTH\n', 401

    auth_token = request.args.get('a', '')
    timestamp = request.args.get('t', 0)

    correct = _get_audioscrobbler_auth_token(user['auth_token'], timestamp)

    if auth_token != _get_audioscrobbler_auth_token(user['auth_token'], timestamp):
        return 'BADAUTH\n', 401

    session = Session.create_by_user_id(user['id'])
    current_app.logger.info('New session created with id: {}'.format(session.sid))

    return '\n'.join([
        'OK',
        session.sid,
        '{}/np_1.2'.format(current_app.config['LASTFM_PROXY_URL']),
        '{}/protocol_1.2\n'.format(current_app.config['LASTFM_PROXY_URL'])
    ])
示例#13
0
def handle_missing_musicbrainz_data(data):
    """ Insert user missing musicbrainz data i.e data submitted to ListenBrainz but not MusicBrainz.
    """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)

    if not user:
        return

    current_app.logger.debug(
        "Inserting missing musicbrainz data for {}".format(musicbrainz_id))

    missing_musicbrainz_data = data['missing_musicbrainz_data']
    source = data['source']

    try:
        db_missing_musicbrainz_data.insert_user_missing_musicbrainz_data(
            user['id'],
            UserMissingMusicBrainzDataJson(
                **{'missing_musicbrainz_data': missing_musicbrainz_data}),
            source)
    except ValidationError:
        current_app.logger.error(
            """ValidationError while inserting missing MusicBrainz data from source "{source}" for user
                                 with musicbrainz_id: {musicbrainz_id}. Data: {data}"""
            .format(musicbrainz_id=musicbrainz_id,
                    data=json.dumps(data, indent=3),
                    source=source),
            exc_info=True)

    current_app.logger.debug(
        "Missing musicbrainz data for {} inserted".format(musicbrainz_id))
示例#14
0
def handle_user_entity(data):
    """ Take entity stats for a user and save it in the database. """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)
    if not user:
        return

    # send a notification if this is a new batch of stats
    if is_new_user_stats_batch():
        notify_user_stats_update(stat_type=data.get('type', ''))
    current_app.logger.debug("inserting stats for user %s", musicbrainz_id)

    stats_range = data['stats_range']
    entity = data['entity']
    data[entity] = data['data']

    # Strip extra data
    to_remove = {'musicbrainz_id', 'type', 'entity', 'data', 'stats_range'}
    data_mod = {key: data[key] for key in data if key not in to_remove}

    entity_model = _get_user_entity_model(entity)

    # Get function to insert statistics
    db_handler = getattr(db_stats, 'insert_user_{}'.format(entity))

    try:
        db_handler(user['id'], entity_model(**{stats_range: data_mod}))
    except ValidationError:
        current_app.logger.error("""ValidationError while inserting {stats_range} top {entity} for user with user_id: {user_id}.
                                 Data: {data}""".format(stats_range=stats_range, entity=entity,
                                                        user_id=user['id'], data=json.dumps({stats_range: data_mod}, indent=3)),
                                 exc_info=True)
示例#15
0
def user_feed(user_name: str):
    db_conn = webserver.create_timescale(current_app)
    min_ts, max_ts, count, time_range = _validate_get_endpoint_params(db_conn, user_name)
    if min_ts is None and max_ts is None:
        max_ts = int(time.time())

    user = db_user.get_by_mb_id(user_name)
    if not user:
        raise APINotFound(f"User {user_name} not found")

    users_following = [user['musicbrainz_id'] for user in db_user_relationship.get_following_for_user(user['id'])]

    listens = db_conn.fetch_listens_for_multiple_users_from_storage(
        users_following,
        limit=count,
        from_ts=min_ts,
        to_ts=max_ts,
        time_range=time_range,
        order=1,  # descending
    )
    listen_data = []
    for listen in listens:
        listen_data.append(listen.to_api())

    return jsonify({'payload': {
        'user_id': user_name,
        'count': len(listen_data),
        'feed': listen_data,
    }})
示例#16
0
def get_listen_count(user_name):
    """
        Get the number of listens for a user ``user_name``.

        The returned listen count has an element 'payload' with only key: 'count'
        which unsurprisingly contains the listen count for the user.

    :statuscode 200: Yay, you have listen counts!
    :statuscode 404: The requested user was 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)

    try:
        listen_count = timescale_connection._ts.get_listen_count_for_user(user["id"])
    except psycopg2.OperationalError as err:
        current_app.logger.error("cannot fetch user listen count: ", str(err))
        raise APIServiceUnavailable(
            "Cannot fetch user listen count right now.")

    return jsonify({'payload': {
        'count': listen_count
    }})
示例#17
0
def get_similar_to_user(user_name, other_user_name):
    """
    Get the similarity of the user and the other user, based on their listening history.
    Returns a single dict:

    {
      "user_name": "other_user",
      "similarity": 0.1938480256
    }

    :param user_name: the MusicBrainz ID of the the one user
    :param other_user_name: the MusicBrainz ID of the other user whose similar users are 
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*
    :statuscode 404: The requested user was not found.
    """
    user = db_user.get_by_mb_id(user_name)
    if not user:
        raise APINotFound("User %s not found" % user_name)

    similar_users = db_user.get_similar_users(user['id'])
    try:
        return jsonify({
            'payload': {
                "user_name": other_user_name,
                "similarity": similar_users.similar_users[other_user_name]
            }
        })
    except KeyError:
        raise APINotFound("Similar-to user not found")
示例#18
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))
示例#19
0
def get_similar_users(user_name):
    """
    Get list of users who have similar music tastes (based on their listen history)
    for a given user. Returns an array of dicts like these:

    {
      "user_name": "hwnrwx",
      "similarity": 0.1938480256
    }

    :param user_name: the MusicBrainz ID of the user whose similar users are being requested.
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*
    :statuscode 404: The requested user was not found.
    """

    user = db_user.get_by_mb_id(user_name)
    if not user:
        raise APINotFound("User %s not found" % user_name)
    similar_users = db_user.get_similar_users(user['id'])

    response = []
    for user_name in similar_users.similar_users:
        response.append({
            'user_name': user_name,
            'similarity': similar_users.similar_users[user_name]
        })
    return jsonify({
        'payload':
        sorted(response, key=itemgetter('similarity'), reverse=True)
    })
示例#20
0
def get_playing_now(user_name):
    """
    Get the listen being played right now for user ``user_name``.

    This endpoint returns a JSON document with a single listen in the same format as the ``/user/<user_name>/listens`` endpoint,
    with one key difference, there will only be one listen returned at maximum and the listen will not contain a ``listened_at`` element.

    The format for the JSON returned is defined in our :ref:`json-doc`.

    :statuscode 200: Yay, you have data!
    :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)

    playing_now_listen = redis_connection._redis.get_playing_now(user['id'])
    listen_data = []
    count = 0
    if playing_now_listen:
        count += 1
        listen_data = [{
            'track_metadata': playing_now_listen.data,
        }]

    return jsonify({
        'payload': {
            'count': count,
            'user_id': user_name,
            'playing_now': True,
            'listens': listen_data,
        },
    })
示例#21
0
def latest_import():
    """
    Get and update the timestamp of the newest listen submitted in previous imports to ListenBrainz.

    In order to get the timestamp for a user, make a GET request to this endpoint. The data returned will
    be JSON of the following format:

    .. code-block:: json

        {
            "musicbrainz_id": "the MusicBrainz ID of the user",
            "latest_import": "the timestamp of the newest listen submitted in previous imports. Defaults to 0"
        }

    :param user_name: the MusicBrainz ID of the user whose data is needed
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*

    In order to update the timestamp of a user, you'll have to provide a user token in the Authorization
    Header. User tokens can be found on https://listenbrainz.org/profile/ .

    The JSON that needs to be posted must contain a field named `ts` in the root with a valid unix timestamp.

    :reqheader Authorization: Token <user token>
    :reqheader Content-Type: *application/json*
    :statuscode 200: latest import timestamp updated
    :statuscode 400: invalid JSON sent, see error message for details.
    :statuscode 401: invalid authorization. See error message for details.
    """
    if request.method == 'GET':
        user_name = request.args.get('user_name', '')
        user = db_user.get_by_mb_id(user_name)
        if user is None:
            raise APINotFound(
                "Cannot find user: {user_name}".format(user_name=user_name))
        return jsonify({
            'musicbrainz_id':
            user['musicbrainz_id'],
            'latest_import':
            0 if not user['latest_import'] else int(
                user['latest_import'].strftime('%s'))
        })
    elif request.method == 'POST':
        user = validate_auth_header()

        try:
            ts = ujson.loads(request.get_data()).get('ts', 0)
        except ValueError:
            raise APIBadRequest('Invalid data sent')

        try:
            db_user.increase_latest_import(user['musicbrainz_id'], int(ts))
        except DatabaseException as e:
            current_app.logger.error(
                "Error while updating latest import: {}".format(e))
            raise APIInternalServerError(
                'Could not update latest_import, try again')

        return jsonify({'status': 'ok'})
示例#22
0
def get_feedback_for_recordings_for_user(user_name):
    """
    Get feedback given by user ``user_name`` for the list of recordings supplied.

    A sample response may look like:

    .. code-block:: json

        {
            "feedback": [
                {
                    "created": 1604033691,
                    "rating": "bad_recommendation",
                    "recording_mbid": "9ffabbe4-e078-4906-80a7-3a02b537e251"
                },
                {
                    "created": 1604032934,
                    "rating": "hate",
                    "recording_mbid": "28111d2c-a80d-418f-8b77-6aba58abe3e7"
                }
            ],
            "user_name": "Vansika Pareek"
        }

    An empty response will be returned if the feedback for given recording MBID doesn't exist.

    :param mbids: comma separated list of recording_mbids for which feedback records are to be fetched.
    :type mbids: ``str``
    :statuscode 200: Yay, you have data!
    :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)

    mbids = request.args.get('mbids')
    if not mbids:
        raise APIBadRequest("Please provide comma separated recording 'mbids'!")

    recording_list = parse_recording_mbid_list(mbids)

    if not len(recording_list):
        raise APIBadRequest("Please provide comma separated recording mbids!")

    try:
        feedback = db_feedback.get_feedback_for_multiple_recordings_for_user(user_id=user["id"], recording_list=recording_list)
    except ValidationError as e:
        log_raise_400("Invalid JSON document submitted: %s" % str(e).replace("\n ", ":").replace("\n", " "),
                      request.args)

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

    return jsonify({
        "feedback": recommendation_feedback,
        "user_name": user_name
    })
示例#23
0
    def test_fetch_email(self):
        musicbrainz_id = "one"
        email = "*****@*****.**"
        user_id = db_user.create(1, musicbrainz_id, email)
        self.assertNotIn("email", db_user.get(user_id))
        self.assertEqual(email,
                         db_user.get(user_id, fetch_email=True)["email"])

        token = db_user.get(user_id)["auth_token"]
        self.assertNotIn("email", db_user.get_by_token(token))
        self.assertEqual(
            email,
            db_user.get_by_token(token, fetch_email=True)["email"])

        self.assertNotIn("email", db_user.get_by_mb_id(musicbrainz_id))
        self.assertEqual(
            email,
            db_user.get_by_mb_id(musicbrainz_id, fetch_email=True)["email"])
示例#24
0
def _get_user(user_name):
    """ Get current username """
    if current_user.is_authenticated and \
       current_user.musicbrainz_id == user_name:
        return current_user
    else:
        user = db_user.get_by_mb_id(user_name)
        if user is None:
            raise NotFound("Cannot find user: %s" % user_name)
        return User.from_dbrow(user)
示例#25
0
def handle_user_artist(data):
    """ Take artist stats for a user and save it in the database.
    """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)
    if not user:
        return
    artists = data['artist_stats']
    artist_count = data['artist_count']
    db_stats.insert_user_stats(user['id'], artists, {}, {}, artist_count)
示例#26
0
def _get_user(user_name):
    """ Get current username """
    if current_user.is_authenticated() and \
       current_user.musicbrainz_id == user_name:
        return current_user
    else:
        user = db_user.get_by_mb_id(user_name)
        if user is None:
            raise NotFound("Cannot find user: %s" % user_name)
        return User.from_dbrow(user)
示例#27
0
def _validate_stats_user_params(user_name) -> Tuple[Dict, str]:
    """ Validate and return the user and common stats params """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound(f"Cannot find user: {user_name}")

    stats_range = request.args.get("range", default="all_time")
    if not _is_valid_range(stats_range):
        raise APIBadRequest(f"Invalid range: {stats_range}")
    return user, stats_range
示例#28
0
def year_in_music(user_name: str):
    """ Get data for year in music stuff """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound(f"Cannot find user: {user_name}")
    return jsonify({
        "payload": {
            "user_name": user_name,
            "data": get_year_in_music(user["id"]) or {}
        }
    })
示例#29
0
def create_user_notification_event(user_name):
    """ Post a message with a link on a user's timeline. Only approved users are allowed to perform this action.

    The request should contain the following data:

    .. code-block:: json

        {
            "metadata": {
                "message": <the message to post, required>,
            }
        }

    :param user_name: The MusicBrainz ID of the user on whose timeline the message is to be posted.
    :type user_name: ``str``
    :statuscode 200: Successful query, message has been posted!
    :statuscode 400: Bad request, check ``response['error']`` for more details.
    :statuscode 403: Forbidden, you are not an approved user.
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*

    """
    creator = validate_auth_header()
    if creator["musicbrainz_id"] not in current_app.config[
            'APPROVED_PLAYLIST_BOTS']:
        raise APIForbidden(
            "Only approved users are allowed to post a message on a user's timeline."
        )

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

    try:
        data = ujson.loads(request.get_data())['metadata']
    except (ValueError, KeyError) as e:
        raise APIBadRequest(f"Invalid JSON: {str(e)}")

    if "message" not in data:
        raise APIBadRequest("Invalid metadata: message is missing")

    message = _filter_description_html(data["message"])
    metadata = NotificationMetadata(creator=creator['musicbrainz_id'],
                                    message=message)

    try:
        db_user_timeline_event.create_user_notification_event(
            user['id'], metadata)
    except DatabaseException:
        raise APIInternalServerError("Something went wrong, please try again.")

    return jsonify({'status': 'ok'})
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
    })
示例#31
0
def latest_import():
    """
    Get and update the timestamp of the newest listen submitted in previous imports to ListenBrainz.

    In order to get the timestamp for a user, make a GET request to this endpoint. The data returned will
    be JSON of the following format:

    {
        'musicbrainz_id': the MusicBrainz ID of the user,

        'latest_import': the timestamp of the newest listen submitted in previous imports. Defaults to 0
    }

    :param user_name: the MusicBrainz ID of the user whose data is needed
    :statuscode 200: Yay, you have data!
    :resheader Content-Type: *application/json*

    In order to update the timestamp of a user, you'll have to provide a user token in the Authorization
    Header. User tokens can be found on https://listenbrainz.org/profile/ .

    The JSON that needs to be posted must contain a field named `ts` in the root with a valid unix timestamp.

    :reqheader Authorization: Token <user token>
    :statuscode 200: latest import timestamp updated
    :statuscode 400: invalid JSON sent, see error message for details.
    :statuscode 401: invalid authorization. See error message for details.
    """
    if request.method == 'GET':
        user_name = request.args.get('user_name', '')
        user = db_user.get_by_mb_id(user_name)
        if user is None:
            raise NotFound("Cannot find user: {user_name}".format(user_name=user_name))
        return jsonify({
                'musicbrainz_id': user['musicbrainz_id'],
                'latest_import': 0 if not user['latest_import'] else int(user['latest_import'].strftime('%s'))
            })
    elif request.method == 'POST':
        user = _validate_auth_header()

        try:
            ts = ujson.loads(request.get_data()).get('ts', 0)
        except ValueError:
            raise BadRequest('Invalid data sent')

        try:
            db_user.increase_latest_import(user['musicbrainz_id'], int(ts))
        except DatabaseException as e:
            current_app.logger.error("Error while updating latest import: {}".format(e))
            raise InternalServerError('Could not update latest_import, try again')

        return jsonify({'status': 'ok'})
示例#32
0
def report_abuse(user_name):
    data = request.json
    reason = None
    if data:
        reason = data.get("reason")
        if not isinstance(reason, str):
            raise APIBadRequest("Reason must be a string.")
    user_to_report = db_user.get_by_mb_id(user_name)
    if current_user.id != user_to_report["id"]:
        db_user.report_user(current_user.id, user_to_report["id"], reason)
        return jsonify(
            {"status": "%s has been reported successfully." % user_name})
    else:
        raise APIBadRequest("You cannot report yourself.")
示例#33
0
def handle_user_artist(data):
    """ Take artist stats for a user and save it in the database.
    """
    musicbrainz_id = data['musicbrainz_id']
    user = db_user.get_by_mb_id(musicbrainz_id)
    if not user:
        return

    # send a notification if this is a new batch of stats
    if is_new_user_stats_batch():
        notify_user_stats_update()
    artists = data['artist_stats']
    artist_count = data['artist_count']
    db_stats.insert_user_stats(user['id'], artists, {}, {}, artist_count)