def pins(user_name: str): """ Show user pin history """ user = _get_user(user_name) user_data = { "name": user.musicbrainz_id, "id": user.id, } pins = get_pin_history_for_user(user_id=user.id, count=25, offset=0) pins = [dict(pin) for pin in fetch_track_metadata_for_items(pins)] total_count = get_pin_count_for_user(user_id=user.id) props = { "user": user_data, "pins": pins, "profile_url": url_for('user.profile', user_name=user_name), "total_count": total_count, "logged_in_user_follows_user": logged_in_user_follows_user(user), } return render_template("user/pins.html", active_section="pins", props=ujson.dumps(props), user=user)
def test_fetch_track_metadata_for_items(self): recordings = self.insert_recordings() models = [ # these recordings test we find mapping metadata from mbids MsidMbidModel(recording_msid=recordings[0]["recording_msid"], recording_mbid=recordings[0]["recording_mbid"]), MsidMbidModel(recording_msid=recordings[1]["recording_msid"], recording_mbid=recordings[1]["recording_mbid"]), # this recording tests loading data from MsB when no mapping is available MsidMbidModel(recording_msid=recordings[2]["recording_msid"], recording_mbid=None), # this recording tests loading mapping metadata from msid but no mbid # (we actually have a mapped mbid in the corresponding recording but omit it here for testing this case) MsidMbidModel(recording_msid=recordings[3]["recording_msid"], recording_mbid=None) ] models = fetch_track_metadata_for_items(models) for model, recording in zip(models, recordings): metadata = model.track_metadata self.assertEqual(metadata["track_name"], recording["title"]) self.assertEqual(metadata["artist_name"], recording["artist"]) if recording["recording_mbid"] is None: # 3rd recording which only present in MsB continue self.assertEqual(metadata["release_name"], recording["release"]) self.assertEqual(metadata["additional_info"]["recording_mbid"], recording["recording_mbid"]) self.assertEqual(metadata["additional_info"]["recording_msid"], recording["recording_msid"]) self.assertEqual(metadata["additional_info"]["release_mbid"], recording["release_mbid"]) self.assertEqual(metadata["additional_info"]["artist_mbids"], recording["artist_mbids"])
def test_fetch_track_metadata_for_items_with_same_mbid(self): recording = self.insert_recordings()[0] models = [ MsidMbidModel(recording_msid=recording["recording_msid"], recording_mbid=recording["recording_mbid"]), MsidMbidModel(recording_msid=recording["recording_msid"], recording_mbid=recording["recording_mbid"]), ] models = fetch_track_metadata_for_items(models) for model in models: metadata = model.track_metadata self.assertEqual(metadata["track_name"], recording["title"]) self.assertEqual(metadata["artist_name"], recording["artist"]) self.assertEqual(metadata["release_name"], recording["release"]) self.assertEqual(metadata["additional_info"]["recording_mbid"], recording["recording_mbid"]) self.assertEqual(metadata["additional_info"]["recording_msid"], recording["recording_msid"]) self.assertEqual(metadata["additional_info"]["release_mbid"], recording["release_mbid"]) self.assertEqual(metadata["additional_info"]["artist_mbids"], recording["artist_mbids"])
def get_feedback_for_user(user_id: int, limit: int, offset: int, score: int = None, metadata: bool = False) -> List[Feedback]: """ Get a list of recording feedback given by the user in descending order of their creation Args: user_id: the row ID of the user in the DB score: the score value by which the results are to be filtered. If 1 then returns the loved recordings, if -1 returns hated recordings. limit: number of rows to be returned offset: number of feedback to skip from the beginning metadata: fetch metadata for the returned feedback recordings Returns: A list of Feedback objects """ args = {"user_id": user_id, "limit": limit, "offset": offset} query = """ SELECT user_id , "user".musicbrainz_id AS user_name , recording_msid::text , recording_mbid::text , score , recording_feedback.created FROM recording_feedback JOIN "user" ON "user".id = recording_feedback.user_id WHERE user_id = :user_id """ if score: query += " AND score = :score" args["score"] = score query += """ ORDER BY recording_feedback.created DESC LIMIT :limit OFFSET :offset """ with db.engine.connect() as connection: result = connection.execute(sqlalchemy.text(query), args) feedback = [Feedback(**dict(row)) for row in result.fetchall()] if metadata and len(feedback) > 0: feedback = fetch_track_metadata_for_items(feedback) return feedback
def get_recording_pin_events(users_for_events: List[dict], min_ts: int, max_ts: int, count: int) -> List[APITimelineEvent]: """ Gets all recording pin events in the feed.""" id_username_map = { user['id']: user['musicbrainz_id'] for user in users_for_events } recording_pin_events_db = get_pins_for_feed( user_ids=(user['id'] for user in users_for_events), min_ts=min_ts, max_ts=max_ts, count=count, ) recording_pin_events_db = fetch_track_metadata_for_items( recording_pin_events_db) events = [] for pin in recording_pin_events_db: try: pinEvent = APIPinEvent( user_name=id_username_map[pin.user_id], blurb_content=pin.blurb_content, track_metadata=TrackMetadata( artist_name=pin.track_metadata["artist_name"], track_name=pin.track_metadata["track_name"], release_name=None, additional_info=AdditionalInfo( recording_msid=pin.recording_msid, recording_mbid=pin.recording_mbid, ))) events.append( APITimelineEvent( id=pin.row_id, event_type=UserTimelineEventType.RECORDING_PIN, user_name=pinEvent.user_name, created=pin.created.timestamp(), metadata=pinEvent, )) except pydantic.ValidationError as e: current_app.logger.error('Validation error: ' + str(e), exc_info=True) continue return events
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_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_items(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, })
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_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_items(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, })
def profile(user_name): # Which database to use to showing user listens. db_conn = webserver.timescale_connection._ts # Which database to use to show playing_now stream. playing_now_conn = webserver.redis_connection._redis user = _get_user(user_name) # User name used to get user may not have the same case as original user name. user_name = user.musicbrainz_id # Getting data for current page max_ts = request.args.get("max_ts") if max_ts is not None: try: max_ts = int(max_ts) except ValueError: raise BadRequest("Incorrect timestamp argument max_ts: %s" % request.args.get("max_ts")) min_ts = request.args.get("min_ts") if min_ts is not None: try: min_ts = int(min_ts) except ValueError: raise BadRequest("Incorrect timestamp argument min_ts: %s" % request.args.get("min_ts")) args = {} if max_ts: args['to_ts'] = max_ts else: args['from_ts'] = min_ts data, min_ts_per_user, max_ts_per_user = db_conn.fetch_listens( user.to_dict(), limit=LISTENS_PER_PAGE, **args) listens = [] for listen in data: listens.append(listen.to_api()) # If there are no previous listens then display now_playing if not listens or listens[0]['listened_at'] >= max_ts_per_user: playing_now = playing_now_conn.get_playing_now(user.id) if playing_now: listens.insert(0, playing_now.to_api()) already_reported_user = False if current_user.is_authenticated: already_reported_user = db_user.is_user_reported( current_user.id, user.id) pin = get_current_pin_for_user(user_id=user.id) if pin: pin = dict(fetch_track_metadata_for_items([pin])[0]) props = { "user": { "id": user.id, "name": user.musicbrainz_id, }, "listens": listens, "latest_listen_ts": max_ts_per_user, "oldest_listen_ts": min_ts_per_user, "profile_url": url_for('user.profile', user_name=user_name), "userPinnedRecording": pin, "web_sockets_server_url": current_app.config['WEBSOCKETS_SERVER_URL'], "logged_in_user_follows_user": logged_in_user_follows_user(user), "already_reported_user": already_reported_user, } return render_template("user/profile.html", props=ujson.dumps(props), user=user, active_section='listens')
def test_pinned_recording_with_metadata(self): recordings = [{ "title": "Strangers", "artist": "Portishead", "release": "Dummy" }, { "title": "Wicked Game", "artist": "Tom Ellis", "release": "Lucifer" }] submitted_data = msb_db.insert_all_in_transaction(recordings) msids = [x["ids"]["recording_msid"] for x in submitted_data] with ts.engine.connect() as connection: query = """ INSERT INTO mbid_mapping_metadata (recording_mbid, release_mbid, release_name, artist_credit_id, artist_mbids, artist_credit_name, recording_name) VALUES ('076255b4-1575-11ec-ac84-135bf6a670e3', '1fd178b4-1575-11ec-b98a-d72392cd8c97', 'Dummy', 65, '{6a221fda-2200-11ec-ac7d-dfa16a57158f}'::UUID[], 'Portishead', 'Strangers') """ connection.execute(sqlalchemy.text(query)) query = """INSERT INTO mbid_mapping (recording_msid, recording_mbid, match_type, last_updated) VALUES (:msid, '076255b4-1575-11ec-ac84-135bf6a670e3', 'exact_match', now())""" connection.execute(sqlalchemy.text(query), msid=msids[0]) pinned_recs = [{ "recording_msid": msids[0], "recording_mbid": "076255b4-1575-11ec-ac84-135bf6a670e3", "blurb_content": "Awesome recordings with mapped data" }, { "recording_msid": msids[1], "recording_mbid": None, "blurb_content": "Great recording but unmapped" }] for data in pinned_recs: db_pinned_rec.pin( WritablePinnedRecording( user_id=self.user["id"], recording_msid=data["recording_msid"], recording_mbid=data["recording_mbid"], blurb_content=data["blurb_content"], )) pins = db_pinned_rec.get_pin_history_for_user(self.user["id"], 5, 0) pins_with_metadata = fetch_track_metadata_for_items(pins) received = [x.dict() for x in pins_with_metadata] # pinned recs returned in reverse order of submitted because order newest to oldest self.assertEqual(received[0]["recording_msid"], pinned_recs[1]["recording_msid"]) self.assertEqual(received[0]["recording_mbid"], pinned_recs[1]["recording_mbid"]) self.assertEqual(received[0]["blurb_content"], pinned_recs[1]["blurb_content"]) self.assertEqual( received[0]["track_metadata"], { "track_name": "Wicked Game", "artist_name": "Tom Ellis", "additional_info": { "recording_msid": msids[1] } }) self.assertEqual(received[1]["recording_msid"], pinned_recs[0]["recording_msid"]) self.assertEqual(received[1]["recording_mbid"], pinned_recs[0]["recording_mbid"]) self.assertEqual(received[1]["blurb_content"], pinned_recs[0]["blurb_content"]) self.assertEqual( received[1]["track_metadata"], { "track_name": "Strangers", "artist_name": "Portishead", "release_name": "Dummy", "additional_info": { "recording_mbid": "076255b4-1575-11ec-ac84-135bf6a670e3", "release_mbid": "1fd178b4-1575-11ec-b98a-d72392cd8c97", "artist_mbids": ["6a221fda-2200-11ec-ac7d-dfa16a57158f"], "recording_msid": msids[0] } })