mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 21:48:18 -08:00
feat: mark album as various artists
This commit is contained in:
parent
dc5dcbd474
commit
57cc60534d
8 changed files with 168 additions and 24 deletions
3
Makefile
3
Makefile
|
|
@ -18,6 +18,9 @@ postgres.start:
|
|||
postgres.stop:
|
||||
docker stop koito-db
|
||||
|
||||
postgres.remove:
|
||||
docker stop koito-db && docker rm koito-db
|
||||
|
||||
api.debug:
|
||||
KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5432?sslmode=disable go run cmd/api/main.go
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,9 @@ function setPrimaryAlias(type: string, id: number, alias: string): Promise<Respo
|
|||
method: "POST"
|
||||
})
|
||||
}
|
||||
function getAlbum(id: number): Promise<Album> {
|
||||
return fetch(`/apis/web/v1/album?id=${id}`).then(r => r.json() as Promise<Album>)
|
||||
}
|
||||
|
||||
function deleteListen(listen: Listen): Promise<Response> {
|
||||
const ms = new Date(listen.time).getTime()
|
||||
|
|
@ -191,6 +194,7 @@ export {
|
|||
deleteApiKey,
|
||||
updateApiKeyLabel,
|
||||
deleteListen,
|
||||
getAlbum,
|
||||
}
|
||||
type Track = {
|
||||
id: number
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createAlias, deleteAlias, getAliases, setPrimaryAlias, type Alias } from "api/api";
|
||||
import { createAlias, deleteAlias, getAliases, getAlbum, setPrimaryAlias, type Album, type Alias } from "api/api";
|
||||
import { Modal } from "./Modal";
|
||||
import { AsyncButton } from "../AsyncButton";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Trash } from "lucide-react";
|
||||
import SetVariousArtists from "./SetVariousArtist";
|
||||
|
||||
interface Props {
|
||||
type: string
|
||||
|
|
@ -12,11 +13,12 @@ interface Props {
|
|||
setOpen: Function
|
||||
}
|
||||
|
||||
export default function RenameModal({ open, setOpen, type, id }: Props) {
|
||||
export default function EditModal({ open, setOpen, type, id }: Props) {
|
||||
const [input, setInput] = useState('')
|
||||
const [loading, setLoading ] = useState(false)
|
||||
const [err, setError ] = useState<string>()
|
||||
const [displayData, setDisplayData] = useState<Alias[]>([])
|
||||
const [variousArtists, setVariousArtists] = useState(false)
|
||||
|
||||
const { isPending, isError, data, error } = useQuery({
|
||||
queryKey: [
|
||||
|
|
@ -38,7 +40,6 @@ export default function RenameModal({ open, setOpen, type, id }: Props) {
|
|||
}
|
||||
}, [data])
|
||||
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<p className="error">Error: {error.message}</p>
|
||||
|
|
@ -49,6 +50,7 @@ export default function RenameModal({ open, setOpen, type, id }: Props) {
|
|||
<p>Loading...</p>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSetPrimary = (alias: string) => {
|
||||
setError(undefined)
|
||||
setLoading(true)
|
||||
|
|
@ -98,6 +100,8 @@ export default function RenameModal({ open, setOpen, type, id }: Props) {
|
|||
|
||||
return (
|
||||
<Modal maxW={1000} isOpen={open} onClose={() => setOpen(false)}>
|
||||
<div className="flex flex-col items-start gap-6 w-full">
|
||||
<div className="w-full">
|
||||
<h2>Alias Manager</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
{displayData.map((v) => (
|
||||
|
|
@ -119,6 +123,11 @@ export default function RenameModal({ open, setOpen, type, id }: Props) {
|
|||
</div>
|
||||
{err && <p className="error">{err}</p>}
|
||||
</div>
|
||||
</div>
|
||||
{ type.toLowerCase() === "album" &&
|
||||
<SetVariousArtists id={id} />
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
79
client/app/components/modals/SetVariousArtist.tsx
Normal file
79
client/app/components/modals/SetVariousArtist.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getAlbum } from "api/api";
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
interface Props {
|
||||
id: number
|
||||
}
|
||||
|
||||
export default function SetVariousArtists({ id }: Props) {
|
||||
const [err, setErr] = useState('')
|
||||
const [va, setVA] = useState(false)
|
||||
const [success, setSuccess] = useState('')
|
||||
|
||||
const { isPending, isError, data, error } = useQuery({
|
||||
queryKey: [
|
||||
'get-album',
|
||||
{
|
||||
id: id
|
||||
},
|
||||
],
|
||||
queryFn: ({ queryKey }) => {
|
||||
const params = queryKey[1] as { id: number };
|
||||
return getAlbum(params.id);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setVA(data.is_various_artists)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<p className="error">Error: {error.message}</p>
|
||||
)
|
||||
}
|
||||
if (isPending) {
|
||||
return (
|
||||
<p>Loading...</p>
|
||||
)
|
||||
}
|
||||
|
||||
const updateVA = (val: boolean) => {
|
||||
setErr('');
|
||||
setSuccess('');
|
||||
fetch(`/apis/web/v1/album?id=${id}&is_various_artists=${val}`, { method: 'PATCH' })
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
setSuccess('Successfully updated album');
|
||||
} else {
|
||||
r.json().then(r => setErr(r.error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<h2>Mark as Various Artists</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
<select
|
||||
name="mark-various-artists"
|
||||
id="mark-various-artists"
|
||||
className="w-30 px-3 py-2 rounded-md"
|
||||
value={va.toString()}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value === 'true';
|
||||
setVA(val);
|
||||
updateVA(val);
|
||||
}}
|
||||
>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
{err && <p className="error">{err}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ import { useAppContext } from "~/providers/AppProvider";
|
|||
import MergeModal from "~/components/modals/MergeModal";
|
||||
import ImageReplaceModal from "~/components/modals/ImageReplaceModal";
|
||||
import DeleteModal from "~/components/modals/DeleteModal";
|
||||
import RenameModal from "~/components/modals/RenameModal";
|
||||
import RenameModal from "~/components/modals/EditModal";
|
||||
import EditModal from "~/components/modals/EditModal";
|
||||
|
||||
export type MergeFunc = (from: number, to: number, replaceImage: boolean) => Promise<Response>
|
||||
export type MergeSearchCleanerFunc = (r: SearchResponse, id: number) => SearchResponse
|
||||
|
|
@ -79,11 +80,11 @@ export default function MediaLayout(props: Props) {
|
|||
</div>
|
||||
{ user &&
|
||||
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">
|
||||
<button title="Rename Item" className="hover:cursor-pointer" onClick={() => setRenameModalOpen(true)}><Edit size={iconSize} /></button>
|
||||
<button title="Edit Item" className="hover:cursor-pointer" onClick={() => setRenameModalOpen(true)}><Edit size={iconSize} /></button>
|
||||
<button title="Replace Image" className="hover:cursor-pointer" onClick={() => setImageModalOpen(true)}><ImageIcon size={iconSize} /></button>
|
||||
<button title="Merge Items" className="hover:cursor-pointer" onClick={() => setMergeModalOpen(true)}><Merge size={iconSize} /></button>
|
||||
<button title="Delete Item" className="hover:cursor-pointer" onClick={() => setDeleteModalOpen(true)}><Trash size={iconSize} /></button>
|
||||
<RenameModal open={renameModalOpen} setOpen={setRenameModalOpen} type={props.type.toLowerCase()} id={props.id}/>
|
||||
<EditModal open={renameModalOpen} setOpen={setRenameModalOpen} type={props.type.toLowerCase()} id={props.id}/>
|
||||
<ImageReplaceModal open={imageModalOpen} setOpen={setImageModalOpen} id={props.imgItemId} musicbrainzId={props.musicbrainzId} type={props.type === "Track" ? "Album" : props.type} />
|
||||
<MergeModal currentTitle={props.title} mergeFunc={props.mergeFunc} mergeCleanerFunc={props.mergeCleanerFunc} type={props.type} currentId={props.id} open={mergeModalOpen} setOpen={setMergeModalOpen} />
|
||||
<DeleteModal open={deleteModalOpen} setOpen={setDeleteModalOpen} title={props.title} id={props.id} type={props.type} />
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ LIMIT $1;
|
|||
UPDATE releases SET musicbrainz_id = $2
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: UpdateReleaseVariousArtists :exec
|
||||
UPDATE releases SET various_artists = $2
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: UpdateReleaseImage :exec
|
||||
UPDATE releases SET image = $2, image_source = $3
|
||||
WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -131,3 +131,46 @@ func MergeArtistsHandler(store db.DB) http.HandlerFunc {
|
|||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateAlbumHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("UpdateAlbumHandler: Received request")
|
||||
|
||||
idStr := r.URL.Query().Get("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
|
||||
valStr := r.URL.Query().Get("is_various_artists")
|
||||
var variousArists bool
|
||||
var updateVariousArtists = false
|
||||
if strings.ToLower(valStr) == "true" {
|
||||
variousArists = true
|
||||
updateVariousArtists = true
|
||||
} else if strings.ToLower(valStr) == "false" {
|
||||
variousArists = false
|
||||
updateVariousArtists = true
|
||||
}
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("UpdateAlbumHandler: Invalid id parameter")
|
||||
utils.WriteError(w, "id is invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = store.UpdateAlbum(ctx, db.UpdateAlbumOpts{
|
||||
ID: int32(id),
|
||||
VariousArtistsUpdate: updateVariousArtists,
|
||||
VariousArtistsValue: variousArists,
|
||||
})
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("UpdateAlbumHandler: Failed to update album")
|
||||
utils.WriteError(w, "failed to update album", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msg("UpdateAlbumHandler: Successfully updated album")
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ func bindRoutes(
|
|||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.ValidateSession(db))
|
||||
r.Post("/replace-image", handlers.ReplaceImageHandler(db))
|
||||
r.Patch("/album", handlers.UpdateAlbumHandler(db))
|
||||
r.Post("/merge/tracks", handlers.MergeTracksHandler(db))
|
||||
r.Post("/merge/albums", handlers.MergeReleaseGroupsHandler(db))
|
||||
r.Post("/merge/artists", handlers.MergeArtistsHandler(db))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue