Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LB-1230: Added ability to edit comment after pinning a Listen #2799

Merged
merged 9 commits into from
May 29, 2024
39 changes: 35 additions & 4 deletions frontend/js/src/pins/PinRecordingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ToastMsg } from "../notifications/Notifications";
export type PinRecordingModalProps = {
recordingToPin: Listen;
onSuccessfulPin?: (pinnedrecording: PinnedRecording) => void;
rowId?: number;
};
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved

export const maxBlurbContentLength = 280;
Expand All @@ -28,8 +29,13 @@ export const maxBlurbContentLength = 280;
*/

export default NiceModal.create(
({ recordingToPin, onSuccessfulPin }: PinRecordingModalProps) => {
({
recordingToPin,
onSuccessfulPin,
rowId,
}: PinRecordingModalProps) => {
const modal = useModal();
const isUpdate = Boolean(rowId);
const [blurbContent, setBlurbContent] = React.useState("");
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved

const { APIService, currentUser } = React.useContext(GlobalAppContext);
Expand Down Expand Up @@ -111,7 +117,30 @@ export default NiceModal.create(
},
[recordingToPin, blurbContent]
);

const updatePinnedRecordingComment = React.useCallback(
async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
try {
if (rowId && recordingToPin && currentUser?.auth_token) {
await APIService.updatePinRecordingBlurbContent(
currentUser.auth_token,
rowId,
blurbContent
);
toast.success(
<ToastMsg
title="Comment updated" message=''/>,
{
toastId: "pin-update-success",
}
);
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (error) {
handleError(error, "Error while updating pinned recording");
}
},
[recordingToPin, blurbContent]
);
const { track_name, artist_name } = recordingToPin.track_metadata;

const unpin_time_ms: number =
Expand Down Expand Up @@ -188,10 +217,12 @@ export default NiceModal.create(
<button
type="submit"
className="btn btn-success"
onClick={submitPinRecording}
onClick={
isUpdate ? updatePinnedRecordingComment : submitPinRecording
}
data-dismiss="modal"
>
Pin track
{isUpdate ? "Update comment" : "Pin track"}
</button>
</div>
</form>
Expand Down
22 changes: 20 additions & 2 deletions frontend/js/src/user/components/PinnedRecordingCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from "react";
import { faThumbtack } from "@fortawesome/free-solid-svg-icons";
import { faPencilAlt, faThumbtack } from "@fortawesome/free-solid-svg-icons";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { toast } from "react-toastify";
import NiceModal from "@ebay/nice-modal-react";
import ListenCard from "../../common/listens/ListenCard";
import ListenControl from "../../common/listens/ListenControl";
import { ToastMsg } from "../../notifications/Notifications";
Expand All @@ -12,6 +13,7 @@ import {
getTrackName,
pinnedRecordingToListen,
} from "../../utils/utils";
import PinRecordingModal from "../../pins/PinRecordingModal";

export type PinnedRecordingCardProps = {
pinnedRecording: PinnedRecording;
Expand Down Expand Up @@ -139,6 +141,7 @@ export default class PinnedRecordingCard extends React.Component<
) : undefined;

const additionalMenuItems = [];
const listen = pinnedRecordingToListen(pinnedRecording);
if (currentlyPinned) {
additionalMenuItems.push(
<ListenControl
Expand All @@ -148,6 +151,21 @@ export default class PinnedRecordingCard extends React.Component<
action={() => this.unpinRecording()}
/>
);
additionalMenuItems.push(
<ListenControl
text="Edit Comment"
key="Edit Comment"
icon={faPencilAlt}
action={() => {
NiceModal.show(PinRecordingModal, {
recordingToPin: listen,
rowId: pinnedRecording.row_id,
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved
});
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved
}}
dataToggle="modal"
dataTarget="#PinRecordingModal"
/>
);
}
additionalMenuItems.push(
<ListenControl
Expand All @@ -168,7 +186,7 @@ export default class PinnedRecordingCard extends React.Component<
return (
<ListenCard
className={cssClasses.join(" ")}
listen={pinnedRecordingToListen(pinnedRecording)}
listen={listen}
showTimestamp
showUsername={false}
additionalMenuItems={additionalMenuItems}
Expand Down
20 changes: 20 additions & 0 deletions frontend/js/src/utils/APIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,26 @@ export default class APIService {
return response.json();
};

updatePinRecordingBlurbContent = async (
userToken: string,
rowId: number,
blurbContent: string
): Promise<{ status: string }> => {
MonkeyDo marked this conversation as resolved.
Show resolved Hide resolved
const url = `${this.APIBaseURI}/pin/update/${rowId}`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Token ${userToken}`,
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({
blurb_content: blurbContent,
}),
});
await this.checkStatus(response);
return response.json();
};

submitMBIDMapping = async (
userToken: string,
recordingMSID: string,
Expand Down
23 changes: 23 additions & 0 deletions listenbrainz/db/pinned_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,26 @@ def get_pin_count_for_user(db_conn, user_id: int) -> int:
})
count = int(result.fetchone().value)
return count


def update_comment(db_conn, row_id: int, blurb_content: str) -> bool:
""" Updates the comment of the user of the current pinned recording

Args:
db_conn: Database connection
user_id: The user for which the comment of pinned record has to be updated
blurb_content: The new comment of the user
Returns:
True if the update was successful, False otherwise
"""
args = {
"blurb_content": blurb_content,
"row_id": row_id
}
result = db_conn.execute(sqlalchemy.text("""
UPDATE pinned_recording
SET blurb_content = :blurb_content
WHERE (id = :row_id)
"""), args)
db_conn.commit()
return result.rowcount == 1
46 changes: 46 additions & 0 deletions listenbrainz/webserver/views/pinned_recording_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,49 @@ def get_current_pin_for_user(user_name):
"pinned_recording": pin,
"user_name": user_name,
})


@pinned_recording_api_bp.route("/pin/update/<row_id>", methods=["POST", "OPTIONS"])
@crossdomain
@ratelimit()
def update_blurb_content_pinned_recording(row_id):
"""
Updates the blurb content of a pinned recording for the user. A user token (found on https://listenbrainz.org/settings/)
must be provided in the Authorization header! Each request should contain only one pinned recording item in the payload.

The format of the JSON to be POSTed to this endpoint should look like the following:

.. code-block:: json

{
"blurb_content": "Wow..",
}

:reqheader Authorization: Token <user token>
:statuscode 200: feedback accepted.
:statuscode 400: invalid JSON sent, see error message for details.
:statuscode 401: invalid authorization. See error message for details.
:resheader Content-Type: *application/json*
"""
validate_auth_header()

data = request.json
try:
row_id = int(row_id)
except ValueError:
log_raise_400("Invalid row_id provided")
if "blurb_content" not in data:
log_raise_400("JSON document must contain blurb_content", data)

try:
blurb_content = data["blurb_content"]
except ValidationError as e:
log_raise_400("Invalid JSON document submitted: %s" % str(e).replace("\n ", ":").replace("\n", " "), data)

try:
status = db_pinned_rec.update_comment(db_conn, row_id, blurb_content)
except Exception as e:
current_app.logger.error("Error while inserting pinned track record: {}".format(e))
raise APIInternalServerError("Something went wrong. Please try again.")

return jsonify({"status": status})