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')))
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))
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)
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))
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))
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"]})
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']) ])
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']) ])
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))
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)
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, }})
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 }})
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")
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))
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) })
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, }, })
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'})
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 })
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"])
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)
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)
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)
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
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 {} } })
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 })
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'})
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.")
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)