refactor: switch from Fetch API to Axios

This commit is contained in:
Pierre
2025-04-10 09:59:43 +02:00
committed by HiItsStolas
parent 66e6ce2545
commit 438a144721
15 changed files with 230 additions and 255 deletions

View File

@@ -17,8 +17,8 @@ class CoverArtArchive extends ExternalAPI {
{
nodeCache: cacheManager.getCache('covertartarchive').data,
rateLimit: {
maxRequests: 20,
maxRPS: 50,
id: 'covertartarchive',
},
}
);

View File

@@ -16,8 +16,8 @@ class ListenBrainzAPI extends ExternalAPI {
{
nodeCache: cacheManager.getCache('listenbrainz').data,
rateLimit: {
maxRequests: 20,
maxRPS: 25,
id: 'listenbrainz',
},
}
);
@@ -25,44 +25,38 @@ class ListenBrainzAPI extends ExternalAPI {
public async getAlbum(mbid: string): Promise<LbAlbumDetails> {
try {
return await this.getRolling<LbAlbumDetails>(
return await this.post<LbAlbumDetails>(
`/album/${mbid}`,
{},
43200,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
baseURL: 'https://listenbrainz.org',
},
'https://listenbrainz.org'
43200
);
} catch (e) {
throw new Error(
`[ListenBrainz] Failed to fetch album details: ${e.message}`
`[ListenBrainz] Failed to fetch album details: ${
e instanceof Error ? e.message : 'Unknown error'
}`
);
}
}
public async getArtist(mbid: string): Promise<LbArtistDetails> {
try {
return await this.getRolling<LbArtistDetails>(
return await this.post<LbArtistDetails>(
`/artist/${mbid}`,
{},
43200,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
baseURL: 'https://listenbrainz.org',
},
'https://listenbrainz.org'
43200
);
} catch (e) {
throw new Error(
`[ListenBrainz] Failed to fetch artist details: ${e.message}`
`[ListenBrainz] Failed to fetch artist details: ${
e instanceof Error ? e.message : 'Unknown error'
}`
);
}
}
@@ -79,9 +73,11 @@ class ListenBrainzAPI extends ExternalAPI {
return this.get<LbTopAlbumsResponse>(
'/stats/sitewide/release-groups',
{
offset: offset.toString(),
range,
count: count.toString(),
params: {
offset: offset.toString(),
range,
count: count.toString(),
},
},
43200
);
@@ -99,9 +95,11 @@ class ListenBrainzAPI extends ExternalAPI {
return this.get<LbTopArtistsResponse>(
'/stats/sitewide/artists',
{
offset: offset.toString(),
range,
count: count.toString(),
params: {
offset: offset.toString(),
range,
count: count.toString(),
},
},
43200
);
@@ -121,10 +119,12 @@ class ListenBrainzAPI extends ExternalAPI {
return this.get<LbFreshReleasesResponse>(
'/explore/fresh-releases',
{
days: days.toString(),
sort,
offset: offset.toString(),
count: count.toString(),
params: {
days: days.toString(),
sort,
offset: offset.toString(),
count: count.toString(),
},
},
43200
);

View File

@@ -1,5 +1,6 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import axios from 'axios';
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
import type { MbAlbumDetails, MbArtistDetails } from './interfaces';
@@ -20,8 +21,8 @@ class MusicBrainz extends ExternalAPI {
},
nodeCache: cacheManager.getCache('musicbrainz').data,
rateLimit: {
maxRequests: 1,
maxRPS: 1,
id: 'musicbrainz',
},
}
);
@@ -45,17 +46,23 @@ class MusicBrainz extends ExternalAPI {
}>(
'/release-group',
{
query,
fmt: 'json',
limit: limit.toString(),
offset: offset.toString(),
params: {
query,
fmt: 'json',
limit: limit.toString(),
offset: offset.toString(),
},
},
43200
);
return data['release-groups'];
} catch (e) {
throw new Error(`[MusicBrainz] Failed to search albums: ${e.message}`);
throw new Error(
`[MusicBrainz] Failed to search albums: ${
e instanceof Error ? e.message : 'Unknown error'
}`
);
}
}
@@ -77,17 +84,23 @@ class MusicBrainz extends ExternalAPI {
}>(
'/artist',
{
query,
fmt: 'json',
limit: limit.toString(),
offset: offset.toString(),
params: {
query,
fmt: 'json',
limit: limit.toString(),
offset: offset.toString(),
},
},
43200
);
return data.artists;
} catch (e) {
throw new Error(`[MusicBrainz] Failed to search artists: ${e.message}`);
throw new Error(
`[MusicBrainz] Failed to search artists: ${
e instanceof Error ? e.message : 'Unknown error'
}`
);
}
}
@@ -111,7 +124,7 @@ class MusicBrainz extends ExternalAPI {
try {
const safeUrl = `https://musicbrainz.org/artist/${artistMbid}/wikipedia-extract`;
const response = await fetch(safeUrl, {
const response = await axios.get(safeUrl, {
headers: {
Accept: 'application/json',
'Accept-Language': language,
@@ -120,11 +133,7 @@ class MusicBrainz extends ExternalAPI {
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const data = response.data;
if (!data.wikipediaExtract || !data.wikipediaExtract.content) {
return null;
}
@@ -161,8 +170,10 @@ class MusicBrainz extends ExternalAPI {
}>(
`/release/${releaseId}`,
{
inc: 'release-groups',
fmt: 'json',
params: {
inc: 'release-groups',
fmt: 'json',
},
},
43200
);
@@ -170,7 +181,9 @@ class MusicBrainz extends ExternalAPI {
return data['release-group']?.id ?? null;
} catch (e) {
throw new Error(
`[MusicBrainz] Failed to fetch release group: ${e.message}`
`[MusicBrainz] Failed to fetch release group: ${
e instanceof Error ? e.message : 'Unknown error'
}`
);
}
}

View File

@@ -120,11 +120,11 @@ export interface LidarrAlbumDetails {
status: string;
duration: number;
trackCount: number;
media: any[];
media: unknown[];
mediumCount: number;
disambiguation: string;
country: any[];
label: any[];
country: unknown[];
label: unknown[];
format: string;
monitored: boolean;
}[];
@@ -136,8 +136,8 @@ export interface LidarrAlbumDetails {
}[];
artist: LidarrArtistDetails & {
artistName: string;
nextAlbum: any | null;
lastAlbum: any | null;
nextAlbum: unknown | null;
lastAlbum: unknown | null;
};
images: LidarrImage[];
links: {
@@ -202,9 +202,9 @@ export interface LidarrAlbumOptions {
mediumCount?: number;
ratings?: LidarrRating;
releaseDate?: string;
releases: any[];
releases: unknown[];
genres: string[];
media: any[];
media: unknown[];
artist: {
status: string;
ended: boolean;
@@ -310,9 +310,11 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
public async removeAlbum(albumId: number): Promise<void> {
try {
await this.delete(`/album/${albumId}`, {
deleteFiles: 'true',
addImportExclusion: 'false',
await this.axios.delete(`/album/${albumId}`, {
params: {
deleteFiles: 'true',
addImportExclusion: 'false',
},
});
logger.info(`[Lidarr] Removed album ${albumId}`);
} catch (e) {
@@ -322,8 +324,10 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
public async searchAlbum(mbid: string): Promise<LidarrAlbumResult[]> {
try {
const data = await this.get<LidarrAlbumResult[]>(`/search`, {
term: `lidarr:${mbid}`,
const data = await this.get<LidarrAlbumResult[]>('/search', {
params: {
term: `lidarr:${mbid}`,
},
});
return data;
} catch (e) {
@@ -334,8 +338,10 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
public async addAlbum(options: LidarrAlbumOptions): Promise<LidarrAlbum> {
try {
const existingAlbums = await this.get<LidarrAlbum[]>('/album', {
foreignAlbumId: options.foreignAlbumId,
includeAllArtistAlbums: 'false',
params: {
foreignAlbumId: options.foreignAlbumId,
includeAllArtistAlbums: 'false',
},
});
if (existingAlbums.length > 0 && existingAlbums[0].monitored) {
@@ -358,7 +364,7 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
}
);
const updatedAlbum = await this.put<LidarrAlbum>(
const updatedAlbum = await this.axios.put<LidarrAlbum>(
`/album/${existingAlbums[0].id}`,
{
...existingAlbums[0],
@@ -368,10 +374,10 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
await this.post('/command', {
name: 'AlbumSearch',
albumIds: [updatedAlbum.id],
albumIds: [updatedAlbum.data.id],
});
return updatedAlbum;
return updatedAlbum.data;
}
const data = await this.post<LidarrAlbum>('/album', {
@@ -389,7 +395,9 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> {
): Promise<LidarrAlbumResult[]> {
try {
const data = await this.get<LidarrAlbumResult[]>('/search', {
term: `lidarr:${mbid}`,
params: {
term: `lidarr:${mbid}`,
},
});
return data;
} catch (e) {

View File

@@ -18,8 +18,8 @@ class TheAudioDb extends ExternalAPI {
{
nodeCache: cacheManager.getCache('tadb').data,
rateLimit: {
maxRequests: 20,
maxRPS: 25,
id: 'tadb',
},
}
);
@@ -103,7 +103,7 @@ class TheAudioDb extends ExternalAPI {
try {
const data = await this.get<TadbArtistResponse>(
`/${this.apiKey}/artist-mb.php`,
{ i: id },
{ params: { i: id } },
this.CACHE_TTL
);

View File

@@ -28,8 +28,8 @@ class TmdbPersonMapper extends ExternalAPI {
{
nodeCache: cacheManager.getCache('tmdb').data,
rateLimit: {
maxRequests: 20,
maxRPS: 50,
id: 'tmdb',
},
}
);
@@ -135,10 +135,12 @@ class TmdbPersonMapper extends ExternalAPI {
const searchResults = await this.get<TmdbSearchPersonResponse>(
'/search/person',
{
query: cleanArtistName,
page: '1',
include_adult: 'false',
language: 'en',
params: {
query: cleanArtistName,
page: '1',
include_adult: 'false',
language: 'en',
},
},
this.CACHE_TTL
);
@@ -316,10 +318,12 @@ class TmdbPersonMapper extends ExternalAPI {
return await this.get<TmdbSearchPersonResponse>(
'/search/person',
{
query: options.query,
page: options.page?.toString() ?? '1',
include_adult: options.includeAdult ? 'true' : 'false',
language: options.language ?? 'en',
params: {
query: options.query,
page: options.page?.toString() ?? '1',
include_adult: options.includeAdult ? 'true' : 'false',
language: options.language ?? 'en',
},
},
this.CACHE_TTL
);

View File

@@ -5,6 +5,7 @@ import { Router } from 'express';
const router = Router();
const caaImageProxy = new ImageProxy('caa', 'https://archive.org/download', {
rateLimitOptions: {
maxRequests: 20,
maxRPS: 50,
},
});

View File

@@ -5,6 +5,7 @@ import { Router } from 'express';
const router = Router();
const tmdbImageProxy = new ImageProxy('tmdb', 'https://image.tmdb.org', {
rateLimitOptions: {
maxRequests: 20,
maxRPS: 50,
},
});

View File

@@ -8,6 +8,7 @@ import Error from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowRightCircleIcon, XCircleIcon } from '@heroicons/react/24/outline';
import { MediaStatus } from '@server/constants/media';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -394,14 +395,15 @@ const ArtistDetails = () => {
try {
const pageSize = Math.min(data?.typeCounts?.[albumType] || 100, 1000);
const response = await fetch(
`/api/v1/artist/${artistId}?albumType=${encodeURIComponent(
albumType
)}&pageSize=${pageSize}`
);
const response = await axios.get(`/api/v1/artist/${artistId}`, {
params: {
albumType,
pageSize,
},
});
if (response.ok) {
const responseData = await response.json();
if (response.status === 200) {
const responseData = response.data;
const validAlbums = responseData.releaseGroups
.filter((album: Album) => album && album.id)
.map((album: Album) => ({

View File

@@ -57,7 +57,9 @@ const BlacklistModal = ({
if (!show) return;
try {
setError(null);
const response = await axios.get(`/api/v1/${type}/${type === 'music' ? mbId : tmdbId}`);
const response = await axios.get(
`/api/v1/${type}/${type === 'music' ? mbId : tmdbId}`
);
setData(response.data);
} catch (err) {
setError(err);

View File

@@ -33,6 +33,7 @@ import { MediaStatus, MediaType } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import type { MusicDetails as MusicDetailsType } from '@server/models/Music';
import type { AlbumResult, ArtistResult } from '@server/models/Search';
import axios from 'axios';
import 'country-flag-icons/3x2/flags.css';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -217,56 +218,41 @@ const MusicDetails = ({ music }: MusicDetailsProps) => {
const onClickWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
const res = await fetch('/api/v1/watchlist', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
try {
const { data } = await axios.post('/api/v1/watchlist', {
mbId: music?.id,
mediaType: MediaType.MUSIC,
title: music?.title,
}),
});
});
if (!res.ok) {
if (data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
}
} catch (error) {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
});
} finally {
setIsUpdating(false);
return;
setToggleWatchlist((prevState) => !prevState);
}
const data = await res.json();
if (data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
}
setIsUpdating(false);
setToggleWatchlist((prevState) => !prevState);
};
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const res = await fetch(`/api/v1/watchlist/${music?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
const response = await axios.delete(`/api/v1/watchlist/${music?.id}`);
if (res.status === 204) {
if (response.status === 204) {
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {
@@ -277,7 +263,7 @@ const MusicDetails = ({ music }: MusicDetailsProps) => {
{ appearance: 'info', autoDismiss: true }
);
}
} catch (e) {
} catch (error) {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
@@ -291,51 +277,46 @@ const MusicDetails = ({ music }: MusicDetailsProps) => {
const onClickHideItemBtn = async (): Promise<void> => {
setIsBlacklistUpdating(true);
const res = await fetch('/api/v1/blacklist', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
try {
const response = await axios.post('/api/v1/blacklist', {
mbId: music?.id,
mediaType: 'music',
title: music?.title,
user: user?.id,
}),
});
});
if (res.status === 201) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
if (response.status === 201) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
revalidate();
} else if (res.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
revalidate();
} else if (response.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title: music?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
}
} catch (error) {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
} finally {
setIsBlacklistUpdating(false);
closeBlacklistModal();
}
setIsBlacklistUpdating(false);
closeBlacklistModal();
};
const showHideButton = hasPermission([Permission.MANAGE_BLACKLIST], {

View File

@@ -11,6 +11,7 @@ import { ArrowRightCircleIcon, XCircleIcon } from '@heroicons/react/24/outline';
import type { MediaStatus } from '@server/constants/media';
import type { PersonCombinedCreditsResponse } from '@server/interfaces/api/personInterfaces';
import type { PersonDetails as PersonDetailsType } from '@server/models/Person';
import axios from 'axios';
import { groupBy, orderBy } from 'lodash';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useMemo, useState } from 'react';
@@ -430,40 +431,32 @@ const PersonDetails = () => {
data?.artist?.typeCounts?.[albumType] || 100,
1000
);
const response = await fetch(
`/api/v1/person/${parsedPersonId}?albumType=${encodeURIComponent(
albumType
)}&pageSize=${pageSize}`
);
if (response.ok) {
const responseData = await response.json();
const validAlbums =
responseData.artist?.releaseGroups
?.filter((album: Album) => album && album.id)
.map((album: Album) => ({
...album,
needsCoverArt: !album.posterPath,
})) || [];
const response = await axios.get(`/api/v1/person/${parsedPersonId}`, {
params: {
albumType: albumType,
pageSize: pageSize,
},
});
setAlbumTypes((prev) => ({
...prev,
[albumType]: {
...prev[albumType],
albums: validAlbums,
isExpanded: true,
isLoading: false,
},
}));
} else {
setAlbumTypes((prev) => ({
...prev,
[albumType]: {
...prev[albumType],
isLoading: false,
},
}));
}
const responseData = response.data;
const validAlbums =
responseData.artist?.releaseGroups
?.filter((album: Album) => album && album.id)
.map((album: Album) => ({
...album,
needsCoverArt: !album.posterPath,
})) || [];
setAlbumTypes((prev) => ({
...prev,
[albumType]: {
...prev[albumType],
albums: validAlbums,
isExpanded: true,
isLoading: false,
},
}));
} catch (error) {
setAlbumTypes((prev) => ({
...prev,

View File

@@ -12,6 +12,7 @@ import type { NonFunctionProperties } from '@server/interfaces/api/common';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import { Permission } from '@server/lib/permissions';
import type { MusicDetails } from '@server/models/Music';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -85,23 +86,17 @@ const MusicRequestModal = ({
tags: requestOverrides.tags,
};
}
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: data?.mbId,
mediaType: 'music',
...overrideParams,
}),
const response = await axios.post<MediaRequest>('/api/v1/request', {
mediaId: data?.mbId,
mediaType: 'music',
...overrideParams,
});
if (!res.ok) throw new Error();
const mediaRequest: MediaRequest = await res.json();
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
if (mediaRequest) {
if (response.data) {
if (onComplete) {
onComplete(
hasPermission(Permission.AUTO_APPROVE)
@@ -133,15 +128,12 @@ const MusicRequestModal = ({
setIsUpdating(true);
try {
const res = await fetch(`/api/v1/request/${editRequest?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
const response = await axios.delete(`/api/v1/request/${editRequest?.id}`);
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
if (res.status === 204) {
if (response.status === 204) {
if (onComplete) {
onComplete(MediaStatus.UNKNOWN);
}
@@ -164,28 +156,19 @@ const MusicRequestModal = ({
setIsUpdating(true);
try {
const res = await fetch(`/api/v1/request/${editRequest?.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaType: 'music',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
}),
await axios.put(`/api/v1/request/${editRequest?.id}`, {
mediaType: 'music',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
});
if (!res.ok) throw new Error();
if (alsoApproveRequest) {
const res = await fetch(`/api/v1/request/${editRequest?.id}/approve`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/request/${editRequest?.id}/approve`);
}
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');

View File

@@ -4,6 +4,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { LidarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -165,28 +166,19 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => {
}) => {
setIsTesting(true);
try {
const response = await fetch('/api/v1/settings/lidarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
const response = await axios.post<TestResponse>(
'/api/v1/settings/lidarr/test',
{
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: TestResponse = await response.json();
}
);
setIsValidated(true);
setTestResponse(data);
setTestResponse(response.data);
if (initialLoad.current) {
addToast(intl.formatMessage(messages.toastLidarrTestSuccess), {
appearance: 'success',
@@ -281,21 +273,13 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => {
)?.name,
};
const response = await fetch(
!lidarr
? '/api/v1/settings/lidarr'
: `/api/v1/settings/lidarr/${lidarr.id}`,
{
method: !lidarr ? 'POST' : 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
if (!lidarr) {
await axios.post('/api/v1/settings/lidarr', submission);
} else {
await axios.put(
`/api/v1/settings/lidarr/${lidarr.id}`,
submission
);
}
onSave();

View File

@@ -131,7 +131,10 @@ const TitleCard = ({
...(mediaType === 'album' ? { mbId: id } : { tmdbId: Number(id) }),
};
const response = await axios.post<Watchlist>('/api/v1/watchlist', requestBody);
const response = await axios.post<Watchlist>(
'/api/v1/watchlist',
requestBody
);
mutate('/api/v1/discover/watchlist');
if (response.data) {
addToast(