Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions client/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,24 @@ function getAlbum(id: number): Promise<Album> {
(r) => r.json() as Promise<Album>,
);
}
function getArtist(id: number): Promise<Artist> {
return fetch(`/apis/web/v1/artist/${id}`).then(
(r) => r.json() as Promise<Artist>,
);
}
function updateExcludeFromTopLists(
type: string,
id: number,
val: boolean,
): Promise<Response> {
return fetch(`/apis/web/v1/${type}/${id}`, {
method: "PATCH",
body: JSON.stringify({ exclude_from_top_lists: val }),
headers: {
"Content-Type": "application/json",
},
});
}

function deleteListen(listen: Listen): Promise<Response> {
const ms = new Date(listen.time).getTime();
Expand Down Expand Up @@ -312,6 +330,8 @@ export {
updateApiKeyLabel,
deleteListen,
getAlbum,
getArtist,
updateExcludeFromTopLists,
getExport,
submitListen,
getRewindStats,
Expand Down Expand Up @@ -352,6 +372,7 @@ type Artist = {
first_listen: number;
is_primary: boolean;
all_time_rank: number;
exclude_from_top_lists: boolean;
};
type Album = {
id: number;
Expand All @@ -364,6 +385,7 @@ type Album = {
time_listened: number;
first_listen: number;
all_time_rank: number;
exclude_from_top_lists: boolean;
};
type Alias = {
id: number;
Expand Down
16 changes: 11 additions & 5 deletions client/app/components/modals/EditModal/EditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Modal } from "../Modal";
import AliasManager from "./AliasManager";
import SetVariousArtists from "./SetVariousArtist";
import SetExcludeFromTopLists from "./SetExcludeFromTopLists";
import UpdateMbzID from "./UpdateMbzID";
import ArtistManager from "./ArtistManager";

Expand All @@ -22,11 +23,16 @@ export default function EditModal({ open, setOpen, type, id }: Props) {
<Modal maxW={1000} isOpen={open} onClose={handleClose}>
<div className="flex flex-col items-start gap-6 w-full">
<AliasManager id={id} type={type} />
{type === "album" && (
<>
<SetVariousArtists id={id} />
</>
)}
<div className="w-full flex flex-row">
{type === "album" && (
<>
<SetVariousArtists id={id} />
</>
)}
{(type === "album" || type === "artist") && (
<SetExcludeFromTopLists id={id} type={type} />
)}
</div>
{type !== "artist" && <ArtistManager id={id} type={type} />}
<UpdateMbzID type={type} id={id} />
</div>
Expand Down
75 changes: 75 additions & 0 deletions client/app/components/modals/EditModal/SetExcludeFromTopLists.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useQuery } from "@tanstack/react-query";
import {
getAlbum,
getArtist,
updateExcludeFromTopLists,
type Album,
type Artist,
} from "api/api";
import { useEffect, useState } from "react";
import SubHeader from "~/components/primitives/SubHeader";

interface Props {
type: string;
id: number;
}

export default function SetExcludeFromTopLists({ type, id }: Props) {
const [err, setErr] = useState("");
const [excluded, setExcluded] = useState(false);
const [success, setSuccess] = useState("");

const { isPending, isError, data, error } = useQuery<Album | Artist>({
queryKey: [type === "artist" ? "get-artist" : "get-album", id],
queryFn: () => (type === "artist" ? getArtist(id) : getAlbum(id)),
});

useEffect(() => {
if (data) {
setExcluded(data.exclude_from_top_lists);
}
}, [data]);

if (isError) {
return <p className="error">Error: {error.message}</p>;
}
if (isPending) {
return <p>Loading...</p>;
}

const updateExcluded = (val: boolean) => {
setErr("");
setSuccess("");
updateExcludeFromTopLists(type, id, val).then((r) => {
if (r.ok) {
setSuccess(`Successfully updated ${type}`);
} else {
r.json().then((r) => setErr(r.error));
}
});
};

return (
<div className="flex-grow">
<SubHeader>Exclude from Top Lists</SubHeader>
<div className="flex flex-col gap-4">
<select
name="exclude-from-top-lists"
id="exclude-from-top-lists"
className="w-30 px-3 py-2 rounded-md"
value={excluded.toString()}
onChange={(e) => {
const val = e.target.value === "true";
setExcluded(val);
updateExcluded(val);
}}
>
<option value="true">True</option>
<option value="false">False</option>
</select>
{err && <p className="error">{err}</p>}
{success && <p className="success">{success}</p>}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function SetVariousArtists({ id }: Props) {
};

return (
<div className="w-full">
<div className="flex-grow">
<SubHeader>Mark as Various Artists</SubHeader>
<div className="flex flex-col gap-4">
<select
Expand Down
9 changes: 9 additions & 0 deletions db/migrations_sqlite/003_exclude_from_top_lists.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- +goose Up

ALTER TABLE artists ADD COLUMN exclude_from_top_lists INTEGER NOT NULL DEFAULT 0;
ALTER TABLE releases ADD COLUMN exclude_from_top_lists INTEGER NOT NULL DEFAULT 0;

-- +goose Down

ALTER TABLE artists DROP COLUMN exclude_from_top_lists;
ALTER TABLE releases DROP COLUMN exclude_from_top_lists;
12 changes: 9 additions & 3 deletions engine/handlers/patch_album.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,17 @@ func UpdateAlbumHandler(store db.AlbumStore) http.HandlerFunc {
}

body, err := utils.DecodeBody[struct {
MBID *string `json:"mbid"`
IsVariousArtists *bool `json:"is_various_artists"`
MBID *string `json:"mbid"`
IsVariousArtists *bool `json:"is_various_artists"`
ExcludeFromTopLists *bool `json:"exclude_from_top_lists"`
}](r)
if err != nil {
l.Debug().Msg("UpdateAlbumHandler: Invalid request body")
utils.WriteError(w, "invalid request body", http.StatusBadRequest)
return
}

if body.MBID == nil && body.IsVariousArtists == nil {
if body.MBID == nil && body.IsVariousArtists == nil && body.ExcludeFromTopLists == nil {
l.Debug().Msg("UpdateAlbumHandler: Request body contains no updatable fields")
utils.WriteError(w, "no updatable fields provided", http.StatusBadRequest)
return
Expand All @@ -126,6 +127,11 @@ func UpdateAlbumHandler(store db.AlbumStore) http.HandlerFunc {
updateOpts.VariousArtistsValue = *body.IsVariousArtists
}

if body.ExcludeFromTopLists != nil {
updateOpts.ExcludeFromTopListsUpdate = true
updateOpts.ExcludeFromTopListsValue = *body.ExcludeFromTopLists
}

if err = store.UpdateAlbum(ctx, updateOpts); err != nil {
l.Error().Err(err).Msg("UpdateAlbumHandler: Failed to update album")
utils.WriteError(w, "failed to update album", http.StatusInternalServerError)
Expand Down
10 changes: 8 additions & 2 deletions engine/handlers/patch_artist.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ func UpdateArtistHandler(store db.ArtistStore) http.HandlerFunc {
}

body, err := utils.DecodeBody[struct {
MBID *string `json:"mbid"`
MBID *string `json:"mbid"`
ExcludeFromTopLists *bool `json:"exclude_from_top_lists"`
}](r)
if err != nil {
l.Debug().Msg("UpdateArtistHandler: Invalid request body")
utils.WriteError(w, "invalid request body", http.StatusBadRequest)
return
}

if body.MBID == nil {
if body.MBID == nil && body.ExcludeFromTopLists == nil {
l.Debug().Msg("UpdateArtistHandler: Request body contains no updatable fields")
utils.WriteError(w, "no updatable fields provided", http.StatusBadRequest)
return
Expand All @@ -80,6 +81,11 @@ func UpdateArtistHandler(store db.ArtistStore) http.HandlerFunc {
updateOpts.MusicBrainzID = mbid
}

if body.ExcludeFromTopLists != nil {
updateOpts.ExcludeFromTopListsUpdate = true
updateOpts.ExcludeFromTopListsValue = *body.ExcludeFromTopLists
}

if err = store.UpdateArtist(ctx, updateOpts); err != nil {
l.Error().Err(err).Msg("UpdateArtistHandler: Failed to update artist")
utils.WriteError(w, "failed to update artist", http.StatusInternalServerError)
Expand Down
Loading