diff --git a/client/api/api.ts b/client/api/api.ts index 27d631a..989202c 100644 --- a/client/api/api.ts +++ b/client/api/api.ts @@ -270,6 +270,19 @@ function setPrimaryAlias( body: form, }); } +function updateMbzId( + type: string, + id: number, + mbzid: string +): Promise { + const form = new URLSearchParams(); + form.append(`${type}_id`, String(id)); + form.append("mbz_id", mbzid); + return fetch(`/apis/web/v1/mbzid`, { + method: "PATCH", + body: form, + }); +} function getAlbum(id: number): Promise { return fetch(`/apis/web/v1/album?id=${id}`).then( (r) => r.json() as Promise @@ -318,6 +331,7 @@ export { createAlias, deleteAlias, setPrimaryAlias, + updateMbzId, getApiKeys, createApiKey, deleteApiKey, diff --git a/client/app/components/modals/EditModal/EditModal.tsx b/client/app/components/modals/EditModal/EditModal.tsx index cbced25..a5c981e 100644 --- a/client/app/components/modals/EditModal/EditModal.tsx +++ b/client/app/components/modals/EditModal/EditModal.tsx @@ -4,6 +4,7 @@ import { deleteAlias, getAliases, setPrimaryAlias, + updateMbzId, type Alias, } from "api/api"; import { Modal } from "../Modal"; @@ -12,6 +13,7 @@ import { useEffect, useState } from "react"; import { Trash } from "lucide-react"; import SetVariousArtists from "./SetVariousArtist"; import SetPrimaryArtist from "./SetPrimaryArtist"; +import UpdateMbzID from "./UpdateMbzID"; interface Props { type: string; @@ -69,7 +71,7 @@ export default function EditModal({ open, setOpen, type, id }: Props) { const handleNewAlias = () => { setError(undefined); if (input === "") { - setError("alias must be provided"); + setError("no input"); return; } setLoading(true); @@ -156,6 +158,7 @@ export default function EditModal({ open, setOpen, type, id }: Props) { {type.toLowerCase() === "track" && ( )} + ); diff --git a/client/app/components/modals/EditModal/UpdateMbzID.tsx b/client/app/components/modals/EditModal/UpdateMbzID.tsx new file mode 100644 index 0000000..0654cc1 --- /dev/null +++ b/client/app/components/modals/EditModal/UpdateMbzID.tsx @@ -0,0 +1,53 @@ +import { updateMbzId } from "api/api"; +import { useState } from "react"; +import { AsyncButton } from "~/components/AsyncButton"; + +interface Props { + type: string; + id: number; +} + +export default function UpdateMbzID({ type, id }: Props) { + const [err, setError] = useState(); + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const [mbzid, setMbzid] = useState<"">(); + const [success, setSuccess] = useState(""); + + const handleUpdateMbzID = () => { + setError(undefined); + if (input === "") { + setError("no input"); + return; + } + setLoading(true); + updateMbzId(type, id, input).then((r) => { + if (r.ok) { + setSuccess("successfully updated MusicBrainz ID"); + } else { + r.json().then((r) => setError(r.error)); + } + }); + setLoading(false); + }; + + return ( +
+

Update MusicBrainz ID

+
+ setInput(e.target.value)} + /> + + Submit + +
+ {err &&

{err}

} + {success &&

{success}

} +
+ ); +} diff --git a/engine/handlers/mbzid.go b/engine/handlers/mbzid.go new file mode 100644 index 0000000..e7aafd8 --- /dev/null +++ b/engine/handlers/mbzid.go @@ -0,0 +1,105 @@ +package handlers + +import ( + "net/http" + "strconv" + + "github.com/gabehf/koito/internal/db" + "github.com/gabehf/koito/internal/logger" + "github.com/gabehf/koito/internal/utils" + "github.com/google/uuid" +) + +func UpdateMbzIdHandler(store db.DB) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + l := logger.FromContext(ctx) + + l.Debug().Msg("UpdateMbzIdHandler: Received request to set update MusicBrainz ID") + + err := r.ParseForm() + if err != nil { + l.Debug().Msg("UpdateMbzIdHandler: Failed to parse form") + utils.WriteError(w, "form is invalid", http.StatusBadRequest) + return + } + + // Parse query parameters + artistIDStr := r.FormValue("artist_id") + albumIDStr := r.FormValue("album_id") + trackIDStr := r.FormValue("track_id") + mbzidStr := r.FormValue("mbz_id") + + if mbzidStr == "" || (artistIDStr == "" && albumIDStr == "" && trackIDStr == "") { + l.Debug().Msg("UpdateMbzIdHandler: Request is missing required parameters") + utils.WriteError(w, "mbzid and artist_id, album_id, or track_id must be provided", http.StatusBadRequest) + return + } + if utils.MoreThanOneString(artistIDStr, albumIDStr, trackIDStr) { + l.Debug().Msg("UpdateMbzIdHandler: Request has more than one of artist_id, album_id, and track_id") + utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided at a time", http.StatusBadRequest) + return + } + var mbzid uuid.UUID + if mbzid, err = uuid.Parse(mbzidStr); err != nil { + l.Debug().Msg("UpdateMbzIdHandler: Provided MusicBrainz ID is invalid") + utils.WriteError(w, "provided musicbrainz id is invalid", http.StatusBadRequest) + return + } + + if artistIDStr != "" { + var artistID int + artistID, err = strconv.Atoi(artistIDStr) + if err != nil { + l.Debug().AnErr("error", err).Msg("UpdateMbzIdHandler: Invalid artist id") + utils.WriteError(w, "invalid artist_id", http.StatusBadRequest) + return + } + err = store.UpdateArtist(ctx, db.UpdateArtistOpts{ + ID: int32(artistID), + MusicBrainzID: mbzid, + }) + if err != nil { + l.Error().Err(err).Msg("UpdateMbzIdHandler: Failed to update musicbrainz id") + utils.WriteError(w, "failed to update musicbrainz id", http.StatusInternalServerError) + return + } + } else if albumIDStr != "" { + var albumID int + albumID, err = strconv.Atoi(albumIDStr) + if err != nil { + l.Debug().AnErr("error", err).Msg("UpdateMbzIdHandler: Invalid album id") + utils.WriteError(w, "invalid artist_id", http.StatusBadRequest) + return + } + err = store.UpdateAlbum(ctx, db.UpdateAlbumOpts{ + ID: int32(albumID), + MusicBrainzID: mbzid, + }) + if err != nil { + l.Error().Err(err).Msg("UpdateMbzIdHandler: Failed to update musicbrainz id") + utils.WriteError(w, "failed to update musicbrainz id", http.StatusInternalServerError) + return + } + } else if trackIDStr != "" { + var trackID int + trackID, err = strconv.Atoi(trackIDStr) + if err != nil { + l.Debug().AnErr("error", err).Msg("UpdateMbzIdHandler: Invalid track id") + utils.WriteError(w, "invalid artist_id", http.StatusBadRequest) + return + } + err = store.UpdateTrack(ctx, db.UpdateTrackOpts{ + ID: int32(trackID), + MusicBrainzID: mbzid, + }) + if err != nil { + l.Error().Err(err).Msg("UpdateMbzIdHandler: Failed to update musicbrainz id") + utils.WriteError(w, "failed to update musicbrainz id", http.StatusInternalServerError) + return + } + } + + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/engine/routes.go b/engine/routes.go index caff228..54100ed 100644 --- a/engine/routes.go +++ b/engine/routes.go @@ -94,6 +94,7 @@ func bindRoutes( r.Post("/aliases", handlers.CreateAliasHandler(db)) r.Post("/aliases/delete", handlers.DeleteAliasHandler(db)) r.Post("/aliases/primary", handlers.SetPrimaryAliasHandler(db)) + r.Patch("/mbzid", handlers.UpdateMbzIdHandler(db)) r.Get("/user/apikeys", handlers.GetApiKeysHandler(db)) r.Post("/user/apikeys", handlers.GenerateApiKeyHandler(db)) r.Patch("/user/apikeys", handlers.UpdateApiKeyLabelHandler(db))