Skip to content

Commit

Permalink
Merge branch 'master' into deprecate-lfm-import
Browse files Browse the repository at this point in the history
  • Loading branch information
MonkeyDo authored Nov 7, 2024
2 parents f493894 + 90453e5 commit 1e0baeb
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 19 deletions.
18 changes: 16 additions & 2 deletions frontend/js/src/artist/ArtistPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ export default function ArtistPage(): JSX.Element {

const releaseGroupTypesNames = Object.entries(groupedReleaseGroups);

// Only show "full discography" button if there are more than 4 rows
// in total across categories, after which we crop the container
const showFullDiscographyButton =
releaseGroupTypesNames.reduce(
(rows, curr) =>
// add up the number of rows (max of 2 rows in the css grid)
rows + (curr[1].length > COVER_ART_SINGLE_ROW_COUNT ? 2 : 1),
0
) > 4;

return (
<div id="entity-page" className="artist-page" role="main">
<Helmet>
Expand Down Expand Up @@ -507,7 +517,11 @@ export default function ArtistPage(): JSX.Element {
</div>
)}
</div>
<div className={`discography ${expandDiscography ? "expanded" : ""}`}>
<div
className={`discography ${
expandDiscography || !showFullDiscographyButton ? "expanded" : ""
}`}
>
{releaseGroupTypesNames.map(([type, rgGroup]) => (
<div className="albums">
<div className="listen-header">
Expand All @@ -525,7 +539,7 @@ export default function ArtistPage(): JSX.Element {
</HorizontalScrollContainer>
</div>
))}
{releaseGroupTypesNames.length >= 2 && (
{showFullDiscographyButton && (
<div className="read-more mb-10">
<button
type="button"
Expand Down
46 changes: 37 additions & 9 deletions frontend/js/src/settings/music-services/details/MusicServices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { capitalize } from "lodash";
import { Link, useLoaderData } from "react-router-dom";
import { toast } from "react-toastify";
import { Helmet } from "react-helmet";
import { format } from "date-fns";
import { ToastMsg } from "../../../notifications/Notifications";
import ServicePermissionButton from "./components/ExternalServiceButton";
import {
Expand All @@ -19,6 +20,10 @@ type MusicServicesLoaderData = {
current_soundcloud_permissions: string;
current_apple_permissions: string;
current_lastfm_permissions: string;
current_lastfm_settings: {
external_user_id?: string;
latest_listened_at?: string;
};
};

export default function MusicServices() {
Expand All @@ -37,9 +42,21 @@ export default function MusicServices() {
critiquebrainz: loaderData.current_critiquebrainz_permissions,
soundcloud: loaderData.current_soundcloud_permissions,
appleMusic: loaderData.current_apple_permissions,
lastFm: loaderData.current_lastfm_permissions,
lastfm: loaderData.current_lastfm_permissions,
});

const [lastfmUserId, setLastfmUserId] = React.useState(
loaderData.current_lastfm_settings?.external_user_id
);
const [lastfmLatestListenedAt, setLastfmLatestListenedAt] = React.useState(
loaderData.current_lastfm_settings?.latest_listened_at
? format(
new Date(loaderData.current_lastfm_settings?.latest_listened_at),
"yyyy-MM-dd'T'HH:mm:ss"
)
: undefined
);

const handlePermissionChange = async (
serviceName: string,
newValue: string
Expand Down Expand Up @@ -169,15 +186,14 @@ export default function MusicServices() {
evt: React.FormEvent<HTMLFormElement>
) => {
evt.preventDefault();
const formData = new FormData(evt.currentTarget);
const username = formData.get("lastfmUsername");
const startdate = formData.get("lastFMStartDatetime");
try {
const response = await fetch(`/settings/music-services/lastfm/connect/`, {
method: "POST",
body: JSON.stringify({
external_user_id: username,
latest_listened_at: startdate || null,
external_user_id: lastfmUserId,
latest_listened_at: lastfmLatestListenedAt
? new Date(lastfmLatestListenedAt).toISOString()
: null,
}),
headers: {
"Content-Type": "application/json",
Expand Down Expand Up @@ -418,6 +434,10 @@ export default function MusicServices() {
name="lastfmUsername"
title="Last.FM Username"
placeholder="Last.FM Username"
value={lastfmUserId}
onChange={(e) => {
setLastfmUserId(e.target.value);
}}
/>
</div>
<div>
Expand All @@ -428,21 +448,29 @@ export default function MusicServices() {
type="datetime-local"
className="form-control"
max={new Date().toISOString()}
value={lastfmLatestListenedAt}
onChange={(e) => {
setLastfmLatestListenedAt(e.target.value);
}}
name="lastFMStartDatetime"
title="Date and time to start import at"
/>
</div>
</div>
<br />
<div className="music-service-selection">
<button type="submit" className="music-service-option">
<button
type="submit"
className="music-service-option"
style={{ width: "100%" }}
>
<input
readOnly
type="radio"
id="lastfm_import"
name="lastfm"
value="import"
checked={permissions.lastFm === "import"}
checked={permissions.lastfm === "import"}
/>
<label htmlFor="lastfm_import">
<div className="title">Connect to Last.FM</div>
Expand Down Expand Up @@ -475,7 +503,7 @@ export default function MusicServices() {
</button>
<ServicePermissionButton
service="lastfm"
current={permissions.lastFm ?? "disable"}
current={permissions.lastfm ?? "disable"}
value="disable"
title="Disable"
details="New scrobbles won't be imported from Last.FM"
Expand Down
16 changes: 10 additions & 6 deletions listenbrainz/db/external_service_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,24 @@ def get_token(db_conn, user_id: int, service: ExternalServiceType) -> Union[dict
service: the service for which the token should be fetched
"""
result = db_conn.execute(sqlalchemy.text("""
SELECT user_id
SELECT "user".id AS user_id
, "user".musicbrainz_id
, "user".musicbrainz_row_id
, service
, eso.service
, access_token
, refresh_token
, last_updated
, eso.last_updated
, token_expires
, scopes
, external_user_id
FROM external_service_oauth
, li.latest_listened_at
FROM external_service_oauth eso
JOIN "user"
ON "user".id = external_service_oauth.user_id
WHERE user_id = :user_id AND service = :service
ON "user".id = eso.user_id
LEFT JOIN listens_importer li
ON li.external_service_oauth_id = eso.id
WHERE "user".id = :user_id
AND eso.service = :service
"""), {
'user_id': user_id,
'service': service.value
Expand Down
1 change: 0 additions & 1 deletion listenbrainz/domain/importer_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def update_latest_listen_ts(self, user_id: int, timestamp: Union[int, float]):
user_id: the ListenBrainz row ID of the user
timestamp: the unix timestamp of the latest listen imported for the user
"""
current_app.logger.info(f"Updating latest_listen_ts for user {user_id}, service {self.service}, timestamp {timestamp}")
listens_importer.update_latest_listened_at(db_conn, user_id, self.service, timestamp)


Expand Down
2 changes: 1 addition & 1 deletion listenbrainz/webserver/views/donors.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def donors_post():
user_playlist_count = db_playlist.get_playlist_count(ts_conn, donor_ids) if donor_ids else {}

for donor in donors:
donor_info = donors_info.get(donor["musicbrainz_id"])
donor_info = donors_info.get(donor["musicbrainz_id"].lower())
if not donor_info:
donor['listenCount'] = None
donor['playlistCount'] = None
Expand Down
13 changes: 13 additions & 0 deletions listenbrainz/webserver/views/entity_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
release_group_bp = Blueprint("release-group", __name__)


def get_release_group_sort_key(release_group):
""" Return a tuple that sorts release group by total_listen_count and then by date """
release_date = release_group.get("date")
if release_date is None:
release_date = datetime.min
else:
release_date = datetime.strptime(release_date, "%Y-%m-%d")

return release_group["total_listen_count"] or 0, release_date


def get_cover_art_for_artist(release_groups):
""" Get the cover art for an artist using a list of their release groups """
covers = []
Expand Down Expand Up @@ -159,6 +170,8 @@ def artist_entity(artist_mbid):
release_group["total_user_count"] = pop["total_user_count"]
release_groups.append(release_group)

release_groups.sort(key=get_release_group_sort_key, reverse=True)

listening_stats = get_entity_listener(db_conn, "artists", artist_mbid, "all_time")
if listening_stats is None:
listening_stats = {
Expand Down
6 changes: 6 additions & 0 deletions listenbrainz/webserver/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ def music_services_details():
"current_lastfm_permissions": current_lastfm_permissions,
}

if lastfm_user:
data["current_lastfm_settings"] = {
"external_user_id": lastfm_user["external_user_id"],
"latest_listened_at": lastfm_user["latest_listened_at"],
}

return jsonify(data)


Expand Down

0 comments on commit 1e0baeb

Please sign in to comment.