refactor: switch from Fetch API to Axios (#1520)

* refactor: switch from Fetch API to Axios

* fix: remove unwanted changes

* fix: rewrite error handling for Axios and remove IPv4 first setting

* style: run prettier

* style: run prettier

* fix: add back custom proxy agent

* fix: add back custom proxy agent

* fix: correct rebase issue

* fix: resolve review comments
This commit is contained in:
Gauthier
2025-04-08 13:20:10 +02:00
committed by GitHub
parent 21400cecdc
commit a488f850f3
112 changed files with 1654 additions and 3032 deletions

View File

@@ -23,6 +23,7 @@ import type {
} from '@server/interfaces/api/blacklistInterfaces';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
@@ -238,11 +239,9 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
setIsUpdating(true);
const res = await fetch('/api/v1/blacklist/' + tmdbId, {
method: 'DELETE',
});
try {
await axios.delete(`/api/v1/blacklist/${tmdbId}`);
if (res.status === 204) {
addToast(
<span>
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
@@ -252,7 +251,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
</span>,
{ appearance: 'success', autoDismiss: true }
);
} else {
} catch {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,

View File

@@ -7,6 +7,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { CalendarIcon, TrashIcon, UserIcon } from '@heroicons/react/24/solid';
import type { Blacklist } from '@server/entity/Blacklist';
import axios from 'axios';
import Link from 'next/link';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -38,11 +39,9 @@ const BlacklistBlock = ({
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
setIsUpdating(true);
const res = await fetch('/api/v1/blacklist/' + tmdbId, {
method: 'DELETE',
});
try {
await axios.delete('/api/v1/blacklist/' + tmdbId);
if (res.status === 204) {
addToast(
<span>
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
@@ -52,7 +51,7 @@ const BlacklistBlock = ({
</span>,
{ appearance: 'success', autoDismiss: true }
);
} else {
} catch {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,

View File

@@ -4,6 +4,7 @@ import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -44,12 +45,8 @@ const BlacklistModal = ({
if (!show) return;
try {
setError(null);
const response = await fetch(`/api/v1/${type}/${tmdbId}`);
if (!response.ok) {
throw new Error();
}
const result = await response.json();
setData(result);
const response = await axios.get(`/api/v1/${type}/${tmdbId}`);
setData(response.data);
} catch (err) {
setError(err);
}

View File

@@ -14,6 +14,7 @@ import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import type { GenreSliderItem } from '@server/interfaces/api/discoverInterfaces';
import type { Keyword, ProductionCompany } from '@server/models/common';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -76,9 +77,11 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
const keywords = await Promise.all(
slider.data.split(',').map(async (keywordId) => {
const res = await fetch(`/api/v1/keyword/${keywordId}`);
const keyword: Keyword = await res.json();
return keyword;
const keyword = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return keyword.data;
})
);
@@ -95,13 +98,15 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return;
}
const res = await fetch(
const response = await axios.get<TmdbGenre[]>(
`/api/v1/genres/${
slider.type === DiscoverSliderType.TMDB_MOVIE_GENRE ? 'movie' : 'tv'
}`
);
const genres: TmdbGenre[] = await res.json();
const genre = genres.find((genre) => genre.id === Number(slider.data));
const genre = response.data.find(
(genre) => genre.id === Number(slider.data)
);
setDefaultDataValue([
{
@@ -116,8 +121,11 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return;
}
const res = await fetch(`/api/v1/studio/${slider.data}`);
const studio: ProductionCompany = await res.json();
const response = await axios.get<ProductionCompany>(
`/api/v1/studio/${slider.data}`
);
const studio = response.data;
setDefaultDataValue([
{
@@ -160,17 +168,16 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
);
const loadKeywordOptions = async (inputValue: string) => {
const res = await fetch(
`/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}`,
const results = await axios.get<TmdbKeywordSearchResponse>(
'/api/v1/search/keyword',
{
headers: {
'Content-Type': 'application/json',
params: {
query: encodeURIExtraParams(inputValue),
},
}
);
const results: TmdbKeywordSearchResponse = await res.json();
return results.results.map((result) => ({
return results.data.results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -181,37 +188,38 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return [];
}
const res = await fetch(
`/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}`,
const results = await axios.get<TmdbCompanySearchResponse>(
'/api/v1/search/company',
{
headers: {
'Content-Type': 'application/json',
params: {
query: encodeURIExtraParams(inputValue),
},
}
);
const results: TmdbCompanySearchResponse = await res.json();
return results.results.map((result) => ({
return results.data.results.map((result) => ({
label: result.name,
value: result.id,
}));
};
const loadMovieGenreOptions = async () => {
const res = await fetch('/api/v1/discover/genreslider/movie');
const results: GenreSliderItem[] = await res.json();
const results = await axios.get<GenreSliderItem[]>(
'/api/v1/discover/genreslider/movie'
);
return results.map((result) => ({
return results.data.map((result) => ({
label: result.name,
value: result.id,
}));
};
const loadTvGenreOptions = async () => {
const res = await fetch('/api/v1/discover/genreslider/tv');
const results: GenreSliderItem[] = await res.json();
const results = await axios.get<GenreSliderItem[]>(
'/api/v1/discover/genreslider/tv'
);
return results.map((result) => ({
return results.data.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -306,31 +314,17 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
onSubmit={async (values, { resetForm }) => {
try {
if (slider) {
const res = await fetch(`/api/v1/settings/discover/${slider.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: Number(values.sliderType),
title: values.title,
data: values.data,
}),
await axios.put(`/api/v1/settings/discover/${slider.id}`, {
type: Number(values.sliderType),
title: values.title,
data: values.data,
});
if (!res.ok) throw new Error();
} else {
const res = await fetch('/api/v1/settings/discover/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: Number(values.sliderType),
title: values.title,
data: values.data,
}),
await axios.post('/api/v1/settings/discover/add', {
type: Number(values.sliderType),
title: values.title,
data: values.data,
});
if (!res.ok) throw new Error();
}
addToast(

View File

@@ -20,6 +20,7 @@ import {
} from '@heroicons/react/24/solid';
import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import axios from 'axios';
import { useRef, useState } from 'react';
import { useDrag, useDrop } from 'react-aria';
import { useIntl } from 'react-intl';
@@ -77,10 +78,7 @@ const DiscoverSliderEdit = ({
const deleteSlider = async () => {
try {
const res = await fetch(`/api/v1/settings/discover/${slider.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/settings/discover/${slider.id}`);
addToast(intl.formatMessage(messages.deletesuccess), {
appearance: 'success',
autoDismiss: true,

View File

@@ -28,6 +28,7 @@ import {
} from '@heroicons/react/24/solid';
import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -75,14 +76,7 @@ const Discover = () => {
const updateSliders = async () => {
try {
const res = await fetch('/api/v1/settings/discover', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(sliders),
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/discover', sliders);
addToast(intl.formatMessage(messages.updatesuccess), {
appearance: 'success',
@@ -100,10 +94,7 @@ const Discover = () => {
const resetSliders = async () => {
try {
const res = await fetch('/api/v1/settings/discover/reset', {
method: 'GET',
});
if (!res.ok) throw new Error();
await axios.get('/api/v1/settings/discover/reset');
addToast(intl.formatMessage(messages.resetsuccess), {
appearance: 'success',

View File

@@ -6,6 +6,7 @@ import defineMessages from '@app/utils/defineMessages';
import { Menu, Transition } from '@headlessui/react';
import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
import type { default as IssueCommentType } from '@server/entity/IssueComment';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { Fragment, useState } from 'react';
@@ -48,10 +49,7 @@ const IssueComment = ({
const deleteComment = async () => {
try {
const res = await fetch(`/api/v1/issueComment/${comment.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/issueComment/${comment.id}`);
} catch (e) {
// something went wrong deleting the comment
} finally {
@@ -178,17 +176,9 @@ const IssueComment = ({
<Formik
initialValues={{ newMessage: comment.message }}
onSubmit={async (values) => {
const res = await fetch(
`/api/v1/issueComment/${comment.id}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: values.newMessage }),
}
);
if (!res.ok) throw new Error();
await axios.put(`/api/v1/issueComment/${comment.id}`, {
message: values.newMessage,
});
if (onUpdate) {
onUpdate();

View File

@@ -27,6 +27,7 @@ import { MediaServerType } from '@server/constants/server';
import type Issue from '@server/entity/Issue';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -121,14 +122,9 @@ const IssueDetails = () => {
const editFirstComment = async (newMessage: string) => {
try {
const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: newMessage }),
await axios.put(`/api/v1/issueComment/${firstComment.id}`, {
message: newMessage,
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toasteditdescriptionsuccess), {
appearance: 'success',
@@ -145,10 +141,7 @@ const IssueDetails = () => {
const updateIssueStatus = async (newStatus: 'open' | 'resolved') => {
try {
const res = await fetch(`/api/v1/issue/${issueData.id}/${newStatus}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/issue/${issueData.id}/${newStatus}`);
addToast(intl.formatMessage(messages.toaststatusupdated), {
appearance: 'success',
@@ -166,10 +159,7 @@ const IssueDetails = () => {
const deleteIssue = async () => {
try {
const res = await fetch(`/api/v1/issue/${issueData.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/issue/${issueData.id}`);
mutate('/api/v1/issue/count');
addToast(intl.formatMessage(messages.toastissuedeleted), {
@@ -504,17 +494,9 @@ const IssueDetails = () => {
}}
validationSchema={CommentSchema}
onSubmit={async (values, { resetForm }) => {
const res = await fetch(
`/api/v1/issue/${issueData?.id}/comment`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: values.message }),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/issue/${issueData?.id}/comment`, {
message: values.message,
});
revalidateIssue();
resetForm();
}}

View File

@@ -11,6 +11,7 @@ import { MediaStatus } from '@server/constants/media';
import type Issue from '@server/entity/Issue';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { Field, Formik } from 'formik';
import Link from 'next/link';
import { useIntl } from 'react-intl';
@@ -100,22 +101,14 @@ const CreateIssueModal = ({
validationSchema={CreateIssueModalSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/issue', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
issueType: values.selectedIssue.issueType,
message: values.message,
mediaId: data?.mediaInfo?.id,
problemSeason: values.problemSeason,
problemEpisode:
values.problemSeason > 0 ? values.problemEpisode : 0,
}),
const newIssue = await axios.post<Issue>('/api/v1/issue', {
issueType: values.selectedIssue.issueType,
message: values.message,
mediaId: data?.mediaInfo?.id,
problemSeason: values.problemSeason,
problemEpisode:
values.problemSeason > 0 ? values.problemEpisode : 0,
});
if (!res.ok) throw new Error();
const newIssue: Issue = await res.json();
if (data) {
addToast(
@@ -126,7 +119,7 @@ const CreateIssueModal = ({
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</div>
<Link href={`/issues/${newIssue.id}`} legacyBehavior>
<Link href={`/issues/${newIssue.data.id}`} legacyBehavior>
<Button as="a" className="mt-4">
<span>{intl.formatMessage(messages.toastviewissue)}</span>
<ArrowRightCircleIcon />

View File

@@ -8,6 +8,7 @@ import {
ClockIcon,
} from '@heroicons/react/24/outline';
import { CogIcon, UserIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import type { LinkProps } from 'next/link';
import Link from 'next/link';
import { forwardRef, Fragment } from 'react';
@@ -38,13 +39,9 @@ const UserDropdown = () => {
const { user, revalidate } = useUser();
const logout = async () => {
const res = await fetch('/api/v1/auth/logout', {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post('/api/v1/auth/logout');
if (data?.status === 'ok') {
if (response.data?.status === 'ok') {
revalidate();
}
};

View File

@@ -2,6 +2,7 @@ import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useIntl } from 'react-intl';
import * as Yup from 'yup';
@@ -57,18 +58,11 @@ const AddEmailModal: React.FC<AddEmailModalProps> = ({
validationSchema={EmailSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
password: password,
email: values.email,
}),
await axios.post('/api/v1/auth/jellyfin', {
username: username,
password: password,
email: values.email,
});
if (!res.ok) throw new Error();
onSave();
} catch (e) {

View File

@@ -5,6 +5,7 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType, ServerType } from '@server/constants/server';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -71,28 +72,14 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
validateOnBlur={false}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
email: values.username,
}),
await axios.post('/api/v1/auth/jellyfin', {
username: values.username,
password: values.password,
email: values.username,
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
let errorMessage = null;
switch (errorData?.message) {
switch (e?.response?.data?.message) {
case ApiErrorCode.InvalidUrl:
errorMessage = messages.invalidurlerror;
break;

View File

@@ -3,6 +3,7 @@ import SensitiveInput from '@app/components/Common/SensitiveInput';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { useState } from 'react';
@@ -54,17 +55,10 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
validateOnBlur={false}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/auth/local', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: values.email,
password: values.password,
}),
await axios.post('/api/v1/auth/local', {
email: values.email,
password: values.password,
});
if (!res.ok) throw new Error();
} catch (e) {
setLoginError(intl.formatMessage(messages.loginerror));
} finally {

View File

@@ -14,6 +14,7 @@ import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import { XCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { useRouter } from 'next/dist/client/router';
import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';
@@ -50,28 +51,13 @@ const Login = () => {
const login = async () => {
setProcessing(true);
try {
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ authToken }),
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
const data = await res.json();
const response = await axios.post('/api/v1/auth/plex', { authToken });
if (data?.id) {
if (response.data?.id) {
revalidate();
}
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
setError(errorData?.message);
setError(e.response?.data?.message);
setAuthToken(undefined);
setProcessing(false);
}

View File

@@ -28,6 +28,7 @@ import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfa
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Link from 'next/link';
import { useIntl } from 'react-intl';
import useSWR from 'swr';
@@ -111,10 +112,7 @@ const ManageSlideOver = ({
const deleteMedia = async () => {
if (data.mediaInfo) {
const res = await fetch(`/api/v1/media/${data.mediaInfo.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
revalidate();
onClose();
}
@@ -122,14 +120,8 @@ const ManageSlideOver = ({
const deleteMediaFile = async () => {
if (data.mediaInfo) {
// we don't check if the response is ok here because there may be no file to delete
await fetch(`/api/v1/media/${data.mediaInfo.id}/file`, {
method: 'DELETE',
});
await fetch(`/api/v1/media/${data.mediaInfo.id}`, {
method: 'DELETE',
});
await axios.delete(`/api/v1/media/${data.mediaInfo.id}/file`);
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
revalidate();
onClose();
}
@@ -158,16 +150,9 @@ const ManageSlideOver = ({
const markAvailable = async (is4k = false) => {
if (data.mediaInfo) {
const res = await fetch(`/api/v1/media/${data.mediaInfo?.id}/available`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
is4k,
}),
await axios.post(`/api/v1/media/${data.mediaInfo?.id}/available`, {
is4k,
});
if (!res.ok) throw new Error();
revalidate();
}
};

View File

@@ -52,6 +52,7 @@ import { IssueStatus } from '@server/constants/issue';
import { MediaStatus, MediaType } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
import axios from 'axios';
import { countries } from 'country-flag-icons';
import 'country-flag-icons/3x2/flags.css';
import { uniqBy } from 'lodash';
@@ -319,41 +320,29 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
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 response = await axios.post('/api/v1/watchlist', {
tmdbId: movie?.id,
mediaType: MediaType.MOVIE,
title: movie?.title,
}),
});
});
if (!res.ok) {
if (response.data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
}
} catch (e) {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
});
setIsUpdating(false);
return;
}
const data = await res.json();
if (data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
}
setIsUpdating(false);
@@ -363,22 +352,17 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const res = await fetch(`/api/v1/watchlist/${movie?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/watchlist/${movie?.id}`);
if (res.status === 204) {
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
}
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} catch (e) {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
@@ -393,21 +377,14 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
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 {
await axios.post('/api/v1/blacklist', {
tmdbId: movie?.id,
mediaType: 'movie',
title: movie?.title,
user: user?.id,
}),
});
});
if (res.status === 201) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
@@ -419,21 +396,23 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
);
revalidate();
} else if (res.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
} catch (e) {
if (e?.response?.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title: movie?.title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
}
}
setIsBlacklistUpdating(false);

View File

@@ -17,6 +17,7 @@ import {
} from '@heroicons/react/24/solid';
import { MediaRequestStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import axios from 'axios';
import Link from 'next/link';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -53,10 +54,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
const updateRequest = async (type: 'approve' | 'decline'): Promise<void> => {
setIsUpdating(true);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/request/${request.id}/${type}`);
if (onUpdate) {
onUpdate();
@@ -67,10 +65,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
const deleteRequest = async () => {
setIsUpdating(true);
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/request/${request.id}`);
if (onUpdate) {
onUpdate();

View File

@@ -13,6 +13,7 @@ import {
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type Media from '@server/entity/Media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import axios from 'axios';
import { useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
import { mutate } from 'swr';
@@ -94,13 +95,9 @@ const RequestButton = ({
request: MediaRequest,
type: 'approve' | 'decline'
) => {
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (data) {
if (response) {
onUpdate();
mutate('/api/v1/request/count');
}
@@ -116,11 +113,7 @@ const RequestButton = ({
await Promise.all(
requests.map(async (request) => {
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
return res.json();
return axios.post(`/api/v1/request/${request.id}/${type}`);
})
);

View File

@@ -23,6 +23,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
import type { NonFunctionProperties } from '@server/interfaces/api/common';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useInView } from 'react-intersection-observer';
@@ -74,10 +75,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
});
const deleteRequest = async () => {
const res = await fetch(`/api/v1/media/${requestData?.media.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
@@ -264,23 +262,16 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
});
const modifyRequest = async (type: 'approve' | 'decline') => {
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (data) {
if (response) {
revalidate();
mutate('/api/v1/request/count');
}
};
const deleteRequest = async () => {
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/request/${request.id}`);
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
};
@@ -289,13 +280,9 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
setRetrying(true);
try {
const res = await fetch(`/api/v1/request/${request.id}/retry`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post(`/api/v1/request/${request.id}/retry`);
if (data) {
if (response) {
revalidate();
}
} catch (e) {

View File

@@ -23,6 +23,7 @@ import type { NonFunctionProperties } from '@server/interfaces/api/common';
import type { RequestResultsResponse } from '@server/interfaces/api/requestInterfaces';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Link from 'next/link';
import { useState } from 'react';
import { useInView } from 'react-intersection-observer';
@@ -65,10 +66,7 @@ const RequestItemError = ({
const { hasPermission } = useUser();
const deleteRequest = async () => {
const res = await fetch(`/api/v1/media/${requestData?.media.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
revalidateList();
mutate('/api/v1/request/count');
};
@@ -328,23 +326,16 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
const [isRetrying, setRetrying] = useState(false);
const modifyRequest = async (type: 'approve' | 'decline') => {
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (data) {
if (response) {
revalidate();
mutate('/api/v1/request/count');
}
};
const deleteRequest = async () => {
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/request/${request.id}`);
revalidateList();
mutate('/api/v1/request/count');
@@ -352,13 +343,8 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
const deleteMediaFile = async () => {
if (request.media) {
// we don't check if the response is ok here because there may be no file to delete
await fetch(`/api/v1/media/${request.media.id}/file`, {
method: 'DELETE',
});
await fetch(`/api/v1/media/${request.media.id}`, {
method: 'DELETE',
});
await axios.delete(`/api/v1/media/${request.media.id}/file`);
await axios.delete(`/api/v1/media/${request.media.id}`);
revalidateList();
}
};
@@ -367,12 +353,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
setRetrying(true);
try {
const res = await fetch(`/api/v1/request/${request.id}/retry`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const result = await res.json();
const result = await axios.post(`/api/v1/request/${request.id}/retry`);
revalidate(result.data);
} catch (e) {
addToast(intl.formatMessage(messages.failedretry), {

View File

@@ -13,6 +13,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import { Permission } from '@server/lib/permissions';
import type { Collection } from '@server/models/Collection';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -198,19 +199,12 @@ const CollectionRequestModal = ({
(
data?.parts.filter((part) => selectedParts.includes(part.id)) ?? []
).map(async (part) => {
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: part.id,
mediaType: 'movie',
is4k,
...overrideParams,
}),
await axios.post<MediaRequest>('/api/v1/request', {
mediaId: part.id,
mediaType: 'movie',
is4k,
...overrideParams,
});
if (!res.ok) throw new Error();
})
);

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 { MovieDetails } from '@server/models/Movie';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -89,24 +90,16 @@ const MovieRequestModal = ({
tags: requestOverrides.tags,
};
}
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: data?.id,
mediaType: 'movie',
is4k,
...overrideParams,
}),
const response = await axios.post<MediaRequest>('/api/v1/request', {
mediaId: data?.id,
mediaType: 'movie',
is4k,
...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(
@@ -154,15 +147,13 @@ const MovieRequestModal = ({
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<MediaRequest>(
`/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);
}
@@ -185,27 +176,17 @@ const MovieRequestModal = ({
setIsUpdating(true);
try {
const res = await fetch(`/api/v1/request/${editRequest?.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaType: 'movie',
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: 'movie',
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

@@ -17,6 +17,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 { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -111,35 +112,22 @@ const TvRequestModal = ({
try {
if (selectedSeasons.length > 0) {
const res = await fetch(`/api/v1/request/${editRequest.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaType: 'tv',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
languageProfileId: requestOverrides?.language,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
seasons: selectedSeasons,
}),
await axios.put(`/api/v1/request/${editRequest.id}`, {
mediaType: 'tv',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
languageProfileId: requestOverrides?.language,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
seasons: selectedSeasons,
});
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`);
}
} else {
const res = await fetch(`/api/v1/request/${editRequest.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
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');
@@ -206,32 +194,23 @@ const TvRequestModal = ({
tags: requestOverrides.tags,
};
}
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: data?.id,
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
mediaType: 'tv',
is4k,
seasons: settings.currentSettings.partialRequestsEnabled
? selectedSeasons.sort((a, b) => a - b)
: getAllSeasons().filter(
(season) => !getAllRequestedSeasons().includes(season)
),
...overrideParams,
}),
const response = await axios.post<MediaRequest>('/api/v1/request', {
mediaId: data?.id,
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
mediaType: 'tv',
is4k,
seasons: settings.currentSettings.partialRequestsEnabled
? selectedSeasons.sort((a, b) => a - b)
: getAllSeasons().filter(
(season) => !getAllRequestedSeasons().includes(season)
),
...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');
if (mediaRequest) {
if (response.data) {
if (onComplete) {
onComplete(mediaRequest.media.status);
onComplete(response.data.media.status);
}
addToast(
<span>

View File

@@ -4,6 +4,7 @@ import PageTitle from '@app/components/Common/PageTitle';
import LanguagePicker from '@app/components/Layout/LanguagePicker';
import defineMessages from '@app/utils/defineMessages';
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
@@ -84,18 +85,14 @@ const ResetPassword = () => {
}}
validationSchema={ResetSchema}
onSubmit={async (values) => {
const res = await fetch(`/api/v1/auth/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
const response = await axios.post(
`/api/v1/auth/reset-password`,
{
email: values.email,
}),
});
if (!res.ok) throw new Error();
}
);
if (res.status === 200) {
if (response.status === 200) {
setSubmitted(true);
}
}}

View File

@@ -5,6 +5,7 @@ import LanguagePicker from '@app/components/Layout/LanguagePicker';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { LifebuoyIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
@@ -99,21 +100,14 @@ const ResetPassword = () => {
}}
validationSchema={ResetSchema}
onSubmit={async (values) => {
const res = await fetch(
const response = await axios.post(
`/api/v1/auth/reset-password/${guid}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: values.password,
}),
password: values.password,
}
);
if (!res.ok) throw new Error();
if (res.status === 200) {
if (response.status === 200) {
setSubmitted(true);
}
}}

View File

@@ -19,6 +19,7 @@ import type {
ProductionCompany,
WatchProviderDetails,
} from '@server/models/common';
import axios from 'axios';
import { orderBy } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -80,9 +81,11 @@ export const CompanySelector = ({
return;
}
const res = await fetch(`/api/v1/studio/${defaultValue}`);
if (!res.ok) throw new Error();
const studio: ProductionCompany = await res.json();
const response = await axios.get<ProductionCompany>(
`/api/v1/studio/${defaultValue}`
);
const studio = response.data;
setDefaultDataValue([
{
@@ -100,15 +103,16 @@ export const CompanySelector = ({
return [];
}
const res = await fetch(
`/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}`
const results = await axios.get<TmdbCompanySearchResponse>(
'/api/v1/search/company',
{
params: {
query: encodeURIExtraParams(inputValue),
},
}
);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const results: TmdbCompanySearchResponse = await res.json();
return results.results.map((result) => ({
return results.data.results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -164,15 +168,11 @@ export const GenreSelector = ({
const genres = defaultValue.split(',');
const res = await fetch(`/api/v1/genres/${type}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const response: TmdbGenre[] = await res.json();
const response = await axios.get<TmdbGenre[]>(`/api/v1/genres/${type}`);
const genreData = genres
.filter((genre) => response.find((gd) => gd.id === Number(genre)))
.map((g) => response.find((gd) => gd.id === Number(g)))
.filter((genre) => response.data.find((gd) => gd.id === Number(genre)))
.map((g) => response.data.find((gd) => gd.id === Number(g)))
.map((g) => ({
label: g?.name ?? '',
value: g?.id ?? 0,
@@ -185,11 +185,11 @@ export const GenreSelector = ({
}, [defaultValue, type]);
const loadGenreOptions = async (inputValue: string) => {
const res = await fetch(`/api/v1/discover/genreslider/${type}`);
if (!res.ok) throw new Error();
const results: GenreSliderItem[] = await res.json();
const results = await axios.get<GenreSliderItem[]>(
`/api/v1/discover/genreslider/${type}`
);
return results
return results.data
.map((result) => ({
label: result.name,
value: result.id,
@@ -309,13 +309,11 @@ export const KeywordSelector = ({
const keywords = await Promise.all(
defaultValue.split(',').map(async (keywordId) => {
const res = await fetch(`/api/v1/keyword/${keywordId}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const keyword: Keyword = await res.json();
const keyword = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return keyword;
return keyword.data;
})
);
@@ -331,15 +329,16 @@ export const KeywordSelector = ({
}, [defaultValue]);
const loadKeywordOptions = async (inputValue: string) => {
const res = await fetch(
`/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}`
const results = await axios.get<TmdbKeywordSearchResponse>(
'/api/v1/search/keyword',
{
params: {
query: encodeURIExtraParams(inputValue),
},
}
);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const results: TmdbKeywordSearchResponse = await res.json();
return results.results.map((result) => ({
return results.data.results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -578,14 +577,10 @@ export const UserSelector = ({
const users = defaultValue.split(',');
const res = await fetch(
const res = await axios.get(
`/api/v1/user?includeIds=${encodeURIComponent(defaultValue)}`
);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const response: UserResultsResponse = await res.json();
const response: UserResultsResponse = res.data;
const genreData = users
.filter((u) => response.results.find((user) => user.id === Number(u)))
@@ -602,11 +597,10 @@ export const UserSelector = ({
}, [defaultValue]);
const loadUserOptions = async (inputValue: string) => {
const res = await fetch(
const res = await axios.get(
`/api/v1/user${inputValue ? `?q=${encodeURIComponent(inputValue)}` : ''}`
);
if (!res.ok) throw new Error();
const results: UserResultsResponse = await res.json();
const results: UserResultsResponse = res.data;
return results.results
.map((result) => ({

View File

@@ -5,6 +5,7 @@ import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -83,24 +84,17 @@ const NotificationsDiscord = () => {
validationSchema={NotificationsDiscordSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/discord', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/discord', {
enabled: values.enabled,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
webhookRoleId: values.webhookRoleId,
enableMentions: values.enableMentions,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
webhookRoleId: values.webhookRoleId,
enableMentions: values.enableMentions,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.discordsettingssaved), {
appearance: 'success',
@@ -139,27 +133,17 @@ const NotificationsDiscord = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/discord/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
webhookRoleId: values.webhookRoleId,
enableMentions: values.enableMentions,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/discord/test', {
enabled: true,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
webhookRoleId: values.webhookRoleId,
enableMentions: values.enableMentions,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -5,6 +5,7 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -147,31 +148,24 @@ const NotificationsEmail = () => {
validationSchema={NotificationsEmailSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
options: {
userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
body: JSON.stringify({
enabled: values.enabled,
options: {
userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.emailsettingssaved), {
@@ -203,33 +197,23 @@ const NotificationsEmail = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/email/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/email/test', {
enabled: true,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -4,6 +4,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -94,22 +95,15 @@ const NotificationsGotify = () => {
validationSchema={NotificationsGotifySchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/gotify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/gotify', {
enabled: values.enabled,
types: values.types,
options: {
url: values.url,
token: values.token,
priority: Number(values.priority),
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
url: values.url,
token: values.token,
priority: Number(values.priority),
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.gotifysettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -147,25 +141,15 @@ const NotificationsGotify = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/gotify/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
url: values.url,
token: values.token,
priority: Number(values.priority),
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/gotify/test', {
enabled: true,
types: values.types,
options: {
url: values.url,
token: values.token,
priority: Number(values.priority),
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -4,6 +4,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -68,21 +69,14 @@ const NotificationsLunaSea = () => {
validationSchema={NotificationsLunaSeaSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/lunasea', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/lunasea', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.settingsSaved), {
appearance: 'success',
autoDismiss: true,
@@ -120,24 +114,14 @@ const NotificationsLunaSea = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/lunasea/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/lunasea/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -5,6 +5,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -67,21 +68,14 @@ const NotificationsPushbullet = () => {
validationSchema={NotificationsPushbulletSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/pushbullet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/pushbullet', {
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushbulletSettingsSaved), {
appearance: 'success',
autoDismiss: true,
@@ -119,24 +113,14 @@ const NotificationsPushbullet = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/pushbullet/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/pushbullet/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -5,6 +5,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import type { PushoverSound } from '@server/api/pushover';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -93,21 +94,14 @@ const NotificationsPushover = () => {
validationSchema={NotificationsPushoverSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/pushover', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/pushover', {
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushoversettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -145,25 +139,16 @@ const NotificationsPushover = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/pushover/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
sound: values.sound,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/pushover/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
sound: values.sound,
},
});
if (toastId) {
removeToast(toastId);
}

View File

@@ -4,6 +4,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -64,20 +65,13 @@ const NotificationsSlack = () => {
validationSchema={NotificationsSlackSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/slack', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/slack', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.slacksettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -115,23 +109,13 @@ const NotificationsSlack = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/slack/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -5,6 +5,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -98,24 +99,17 @@ const NotificationsTelegram = () => {
validationSchema={NotificationsTelegramSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/telegram', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/telegram', {
enabled: values.enabled,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
messageThreadId: values.messageThreadId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
messageThreadId: values.messageThreadId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.telegramsettingssaved), {
appearance: 'success',
@@ -154,27 +148,17 @@ const NotificationsTelegram = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/telegram/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
messageThreadId: values.messageThreadId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/telegram/test', {
enabled: true,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
messageThreadId: values.messageThreadId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
});
if (toastId) {
removeToast(toastId);

View File

@@ -4,6 +4,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -57,17 +58,10 @@ const NotificationsWebPush = () => {
}}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/webpush', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
options: {},
}),
await axios.post('/api/v1/settings/notifications/webpush', {
enabled: values.enabled,
options: {},
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.webpushsettingssaved), {
appearance: 'success',
@@ -98,20 +92,10 @@ const NotificationsWebPush = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/webpush/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
options: {},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/webpush/test', {
enabled: true,
options: {},
});
if (toastId) {
removeToast(toastId);

View File

@@ -8,6 +8,7 @@ import {
ArrowPathIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import dynamic from 'next/dynamic';
import Link from 'next/link';
@@ -149,22 +150,15 @@ const NotificationsWebhook = () => {
validationSchema={NotificationsWebhookSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/notifications/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/notifications/webhook', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.webhooksettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -213,25 +207,16 @@ const NotificationsWebhook = () => {
toastId = id;
}
);
const res = await fetch(
'/api/v1/settings/notifications/webhook/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/notifications/webhook/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
});
if (toastId) {
removeToast(toastId);
}

View File

@@ -16,6 +16,7 @@ import type {
RadarrSettings,
SonarrSettings,
} from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -95,24 +96,19 @@ const OverrideRuleModal = ({
}) => {
setIsTesting(true);
try {
const res = await fetch('/api/v1/settings/sonarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
const response = await axios.post<DVRTestResponse>(
'/api/v1/settings/sonarr/test',
{
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}),
});
if (!res.ok) throw new Error();
const data: DVRTestResponse = await res.json();
}
);
setIsValidated(true);
setTestResponse(data);
setTestResponse(response.data);
} catch (e) {
setIsValidated(false);
} finally {
@@ -179,27 +175,13 @@ const OverrideRuleModal = ({
sonarrServiceId: values.sonarrServiceId,
};
if (!rule) {
const res = await fetch('/api/v1/overrideRule', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/overrideRule', submission);
addToast(intl.formatMessage(messages.ruleCreated), {
appearance: 'success',
autoDismiss: true,
});
} else {
const res = await fetch(`/api/v1/overrideRule/${rule.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.put(`/api/v1/overrideRule/${rule.id}`, submission);
addToast(intl.formatMessage(messages.ruleUpdated), {
appearance: 'success',
autoDismiss: true,

View File

@@ -12,6 +12,7 @@ import type {
SonarrSettings,
} from '@server/lib/settings';
import type { Keyword } from '@server/models/common';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import useSWR from 'swr';
@@ -64,34 +65,26 @@ const OverrideRuleTiles = ({
for (const service of services) {
const { hostname, port, apiKey, baseUrl, useSsl = false } = service;
try {
const res = await fetch(
const response = await axios.post<DVRTestResponse>(
`/api/v1/settings/${
radarrServices.includes(service as RadarrSettings)
? 'radarr'
: 'sonarr'
}/test`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}),
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}
);
if (!res.ok) throw new Error();
const data: DVRTestResponse = await res.json();
results.push({
type: radarrServices.includes(service as RadarrSettings)
? 'radarr'
: 'sonarr',
id: service.id,
...data,
...response.data,
});
} catch {
results.push({
@@ -120,9 +113,8 @@ const OverrideRuleTiles = ({
.flat()
.filter((keywordId) => keywordId)
.map(async (keywordId) => {
const res = await fetch(`/api/v1/keyword/${keywordId}`);
if (!res.ok) throw new Error();
const keyword: Keyword = await res.json();
const response = await axios.get(`/api/v1/keyword/${keywordId}`);
const keyword: Keyword = response.data;
return keyword;
})
);
@@ -132,11 +124,10 @@ const OverrideRuleTiles = ({
.filter((users) => users)
.join(',');
if (allUsersFromRules) {
const res = await fetch(
const response = await axios.get(
`/api/v1/user?includeIds=${encodeURIComponent(allUsersFromRules)}`
);
if (!res.ok) throw new Error();
const users: User[] = (await res.json()).results;
const users: User[] = response.data.results;
setUsers(users);
}
setUsers(users);
@@ -295,10 +286,7 @@ const OverrideRuleTiles = ({
<div className="-ml-px flex w-0 flex-1">
<button
onClick={async () => {
const res = await fetch(`/api/v1/overrideRule/${rule.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/overrideRule/${rule.id}`);
revalidate();
}}
className="focus:ring-blue relative inline-flex w-0 flex-1 items-center justify-center rounded-br-lg border border-transparent py-4 text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out hover:text-white focus:z-10 focus:border-gray-500 focus:outline-none"

View File

@@ -5,6 +5,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { RadarrSettings } 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';
@@ -154,24 +155,19 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
}) => {
setIsTesting(true);
try {
const res = await fetch('/api/v1/settings/radarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
const response = await axios.post<RadarrTestResponse>(
'/api/v1/settings/radarr/test',
{
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}),
});
if (!res.ok) throw new Error();
const data = await res.json();
}
);
setIsValidated(true);
setTestResponse(data);
setTestResponse(response.data);
if (initialLoad.current) {
addToast(intl.formatMessage(messages.toastRadarrTestSuccess), {
appearance: 'success',
@@ -264,23 +260,12 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
tagRequests: values.tagRequests,
};
if (!radarr) {
const res = await fetch('/api/v1/settings/radarr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/radarr', submission);
} else {
const res = await fetch(`/api/v1/settings/radarr/${radarr.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.put(
`/api/v1/settings/radarr/${radarr.id}`,
submission
);
}
onSave();

View File

@@ -10,6 +10,7 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType } from '@server/constants/server';
import type { JellyfinSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
@@ -178,25 +179,13 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
}
try {
const searchParams = new URLSearchParams({
sync: params.sync ? 'true' : 'false',
...(params.enable ? { enable: params.enable } : {}),
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
const res = await fetch(
`/api/v1/settings/jellyfin/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error(res.statusText, { cause: res });
setIsSyncing(false);
revalidate();
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
if (errorData?.message === 'SYNC_ERROR_GROUPED_FOLDERS') {
if (e?.response?.data?.message === 'SYNC_ERROR_GROUPED_FOLDERS') {
toasts.addToast(
intl.formatMessage(
messages.jellyfinSyncFailedAutomaticGroupedFolders
@@ -206,7 +195,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
appearance: 'warning',
}
);
} else if (errorData?.message === 'SYNC_ERROR_NO_LIBRARIES') {
} else if (e?.response?.data?.message === 'SYNC_ERROR_NO_LIBRARIES') {
toasts.addToast(
intl.formatMessage(messages.jellyfinSyncFailedNoLibrariesFound),
{
@@ -229,32 +218,16 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
};
const startScan = async () => {
const res = await fetch('/api/v1/settings/jellyfin/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
start: true,
}),
await axios.post('/api/v1/settings/jellyfin/sync', {
start: true,
});
if (!res.ok) throw new Error();
revalidateSync();
};
const cancelScan = async () => {
const res = await fetch('/api/v1/settings/jellyfin/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cancel: true,
}),
await axios.post('/api/v1/settings/jellyfin/sync', {
cancel: true,
});
if (!res.ok) throw new Error();
revalidateSync();
};
@@ -269,19 +242,15 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
.join(',');
}
const searchParams = new URLSearchParams(params.enable ? params : {});
const res = await fetch(
`/api/v1/settings/jellyfin/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
} else {
const searchParams = new URLSearchParams({
enable: [...activeLibraries, libraryId].join(','),
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
} else {
await axios.get('/api/v1/settings/jellyfin/library', {
params: {
enable: [...activeLibraries, libraryId].join(','),
},
});
const res = await fetch(
`/api/v1/settings/jellyfin/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
}
if (onComplete) {
onComplete();
@@ -491,22 +460,15 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
validationSchema={JellyfinSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
urlBase: values.urlBase,
externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
apiKey: values.apiKey,
} as JellyfinSettings),
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
await axios.post('/api/v1/settings/jellyfin', {
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
urlBase: values.urlBase,
externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
apiKey: values.apiKey,
} as JellyfinSettings);
addToast(
intl.formatMessage(
@@ -519,14 +481,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
}
);
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
if (errorData?.message === ApiErrorCode.InvalidUrl) {
if (e?.response?.data?.message === ApiErrorCode.InvalidUrl) {
addToast(
intl.formatMessage(
messages.invalidurlerror,

View File

@@ -20,6 +20,7 @@ import type {
CacheResponse,
} from '@server/interfaces/api/settingsInterfaces';
import type { JobId } from '@server/lib/settings';
import axios from 'axios';
import cronstrue from 'cronstrue/i18n';
import { Fragment, useReducer, useState } from 'react';
import type { MessageDescriptor } from 'react-intl';
@@ -189,10 +190,7 @@ const SettingsJobs = () => {
}
const runJob = async (job: Job) => {
const res = await fetch(`/api/v1/settings/jobs/${job.id}/run`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/settings/jobs/${job.id}/run`);
addToast(
intl.formatMessage(messages.jobstarted, {
jobname: intl.formatMessage(messages[job.id] ?? messages.unknownJob),
@@ -206,10 +204,7 @@ const SettingsJobs = () => {
};
const cancelJob = async (job: Job) => {
const res = await fetch(`/api/v1/settings/jobs/${job.id}/cancel`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/settings/jobs/${job.id}/cancel`);
addToast(
intl.formatMessage(messages.jobcancelled, {
jobname: intl.formatMessage(messages[job.id] ?? messages.unknownJob),
@@ -223,10 +218,7 @@ const SettingsJobs = () => {
};
const flushCache = async (cache: CacheItem) => {
const res = await fetch(`/api/v1/settings/cache/${cache.id}/flush`, {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post(`/api/v1/settings/cache/${cache.id}/flush`);
addToast(
intl.formatMessage(messages.cacheflushed, { cachename: cache.name }),
{
@@ -253,19 +245,12 @@ const SettingsJobs = () => {
}
setIsSaving(true);
const res = await fetch(
await axios.post(
`/api/v1/settings/jobs/${jobModalState.job.id}/schedule`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
schedule: jobScheduleCron.join(' '),
}),
schedule: jobScheduleCron.join(' '),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.jobScheduleEditSaved), {
appearance: 'success',

View File

@@ -16,6 +16,7 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { ArrowPathIcon } from '@heroicons/react/24/solid';
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
import type { MainSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -84,10 +85,7 @@ const SettingsMain = () => {
const regenerate = async () => {
try {
const res = await fetch('/api/v1/settings/main/regenerate', {
method: 'POST',
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/main/regenerate');
revalidate();
addToast(intl.formatMessage(messages.toastApiKeySuccess), {
@@ -140,25 +138,18 @@ const SettingsMain = () => {
validationSchema={MainSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
hideAvailable: values.hideAvailable,
locale: values.locale,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
enableSpecialEpisodes: values.enableSpecialEpisodes,
cacheImages: values.cacheImages,
}),
await axios.post('/api/v1/settings/main', {
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
hideAvailable: values.hideAvailable,
locale: values.locale,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
enableSpecialEpisodes: values.enableSpecialEpisodes,
cacheImages: values.cacheImages,
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
mutate('/api/v1/status');

View File

@@ -7,6 +7,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { NetworkSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -38,13 +39,9 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
validationProxyPort: 'You must provide a valid port',
advancedNetworkSettings: 'Advanced Network Settings',
networkDisclaimer:
'Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.',
docs: 'documentation',
forceIpv4First: 'Force IPv4 Resolution First',
forceIpv4FirstTip:
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
});
const SettingsNetwork = () => {
@@ -89,7 +86,6 @@ const SettingsNetwork = () => {
<Formik
initialValues={{
csrfProtection: data?.csrfProtection,
forceIpv4First: data?.forceIpv4First,
trustProxy: data?.trustProxy,
proxyEnabled: data?.proxy?.enabled,
proxyHostname: data?.proxy?.hostname,
@@ -104,28 +100,20 @@ const SettingsNetwork = () => {
validationSchema={NetworkSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/network', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
await axios.post('/api/v1/settings/network', {
csrfProtection: values.csrfProtection,
trustProxy: values.trustProxy,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
body: JSON.stringify({
csrfProtection: values.csrfProtection,
forceIpv4First: values.forceIpv4First,
trustProxy: values.trustProxy,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
@@ -381,46 +369,6 @@ const SettingsNetwork = () => {
</div>
</>
)}
<h3 className="heading mt-10">
{intl.formatMessage(messages.advancedNetworkSettings)}
</h3>
<p className="description">
{intl.formatMessage(messages.networkDisclaimer, {
docs: (
<a
href="https://docs.jellyseerr.dev/troubleshooting"
target="_blank"
rel="noreferrer"
className="text-white"
>
{intl.formatMessage(messages.docs)}
</a>
),
})}
</p>
<div className="form-row">
<label htmlFor="forceIpv4First" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.forceIpv4First)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<SettingsBadge badgeType="experimental" />
<span className="label-tip">
{intl.formatMessage(messages.forceIpv4FirstTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="forceIpv4First"
name="forceIpv4First"
onChange={() => {
setFieldValue('forceIpv4First', !values.forceIpv4First);
}}
/>
</div>
</div>
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -16,6 +16,7 @@ import {
} from '@heroicons/react/24/solid';
import type { PlexDevice } from '@server/interfaces/api/plexInterfaces';
import type { PlexSettings, TautulliSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { orderBy } from 'lodash';
import { useMemo, useState } from 'react';
@@ -243,15 +244,9 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
params.enable = activeLibraries.join(',');
}
const searchParams = new URLSearchParams({
sync: params.sync ? 'true' : 'false',
...(params.enable ? { enable: params.enable } : {}),
await axios.get('/api/v1/settings/plex/library', {
params,
});
const res = await fetch(
`/api/v1/settings/plex/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
setIsSyncing(false);
revalidate();
};
@@ -270,12 +265,11 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
toastId = id;
}
);
const res = await fetch('/api/v1/settings/plex/devices/servers');
if (!res.ok) throw new Error();
const data: PlexDevice[] = await res.json();
if (data) {
setAvailableServers(data);
const response = await axios.get<PlexDevice[]>(
'/api/v1/settings/plex/devices/servers'
);
if (response.data) {
setAvailableServers(response.data);
}
if (toastId) {
removeToast(toastId);
@@ -298,30 +292,16 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
};
const startScan = async () => {
const res = await fetch('/api/v1/settings/plex/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
start: true,
}),
await axios.post('/api/v1/settings/plex/sync', {
start: true,
});
if (!res.ok) throw new Error();
revalidateSync();
};
const cancelScan = async () => {
const res = await fetch('/api/v1/settings/plex/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cancel: true,
}),
await axios.post('/api/v1/settings/plex/sync', {
cancel: true,
});
if (!res.ok) throw new Error();
revalidateSync();
};
@@ -336,19 +316,15 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
.join(',');
}
const searchParams = new URLSearchParams(params.enable ? params : {});
const res = await fetch(
`/api/v1/settings/plex/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
} else {
const searchParams = new URLSearchParams({
enable: [...activeLibraries, libraryId].join(','),
await axios.get('/api/v1/settings/plex/library', {
params,
});
} else {
await axios.get('/api/v1/settings/plex/library', {
params: {
enable: [...activeLibraries, libraryId].join(','),
},
});
const res = await fetch(
`/api/v1/settings/plex/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
}
if (onComplete) {
@@ -416,19 +392,12 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
toastId = id;
}
);
const res = await fetch('/api/v1/settings/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
webAppUrl: values.webAppUrl,
} as PlexSettings),
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/plex', {
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
webAppUrl: values.webAppUrl,
} as PlexSettings);
syncLibraries();
@@ -782,27 +751,14 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
validationSchema={TautulliSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/tautulli', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
hostname: values.tautulliHostname,
port: Number(values.tautulliPort),
useSsl: values.tautulliUseSsl,
urlBase: values.tautulliUrlBase,
apiKey: values.tautulliApiKey,
externalUrl: values.tautulliExternalUrl,
} as TautulliSettings),
});
if (!res.ok) throw new Error();
if (!res.ok) {
throw new Error('Failed to fetch');
}
// Continue with any necessary processing
await axios.post('/api/v1/settings/tautulli', {
hostname: values.tautulliHostname,
port: Number(values.tautulliPort),
useSsl: values.tautulliUseSsl,
urlBase: values.tautulliUrlBase,
apiKey: values.tautulliApiKey,
externalUrl: values.tautulliExternalUrl,
} as TautulliSettings);
addToast(
intl.formatMessage(messages.toastTautulliSettingsSuccess),

View File

@@ -17,6 +17,7 @@ import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import type OverrideRule from '@server/entity/OverrideRule';
import type { OverrideRuleResultsResponse } from '@server/interfaces/api/overrideRuleInterfaces';
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Fragment, useState } from 'react';
import { useIntl } from 'react-intl';
import useSWR, { mutate } from 'swr';
@@ -248,14 +249,9 @@ const SettingsServices = () => {
});
const deleteServer = async () => {
const res = await fetch(
`/api/v1/settings/${deleteServerModal.type}/${deleteServerModal.serverId}`,
{
method: 'DELETE',
}
await axios.delete(
`/api/v1/settings/${deleteServerModal.type}/${deleteServerModal.serverId}`
);
if (!res.ok) throw new Error();
setDeleteServerModal({ open: false, serverId: null, type: 'radarr' });
revalidateRadarr();
revalidateSonarr();

View File

@@ -10,6 +10,7 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { MediaServerType } from '@server/constants/server';
import type { MainSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -116,29 +117,22 @@ const SettingsUsers = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
localLogin: values.localLogin,
mediaServerLogin: values.mediaServerLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
quotaLimit: values.movieQuotaLimit,
quotaDays: values.movieQuotaDays,
},
tv: {
quotaLimit: values.tvQuotaLimit,
quotaDays: values.tvQuotaDays,
},
await axios.post('/api/v1/settings/main', {
localLogin: values.localLogin,
mediaServerLogin: values.mediaServerLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
quotaLimit: values.movieQuotaLimit,
quotaDays: values.movieQuotaDays,
},
defaultPermissions: values.defaultPermissions,
}),
tv: {
quotaLimit: values.tvQuotaLimit,
quotaDays: values.tvQuotaDays,
},
},
defaultPermissions: values.defaultPermissions,
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.toastSettingsSuccess), {

View File

@@ -5,6 +5,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { SonarrSettings } 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';
@@ -163,24 +164,19 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
}) => {
setIsTesting(true);
try {
const res = await fetch('/api/v1/settings/sonarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
const response = await axios.post<SonarrTestResponse>(
'/api/v1/settings/sonarr/test',
{
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}),
});
if (!res.ok) throw new Error();
const data: SonarrTestResponse = await res.json();
}
);
setIsValidated(true);
setTestResponse(data);
setTestResponse(response.data);
if (initialLoad.current) {
addToast(intl.formatMessage(messages.toastSonarrTestSuccess), {
appearance: 'success',
@@ -297,23 +293,12 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
tagRequests: values.tagRequests,
};
if (!sonarr) {
const res = await fetch('/api/v1/settings/sonarr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.post('/api/v1/settings/sonarr', submission);
} else {
const res = await fetch(`/api/v1/settings/sonarr/${sonarr.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
await axios.put(
`/api/v1/settings/sonarr/${sonarr.id}`,
submission
);
}
onSave();

View File

@@ -4,6 +4,7 @@ import defineMessages from '@app/utils/defineMessages';
import { InformationCircleIcon } from '@heroicons/react/24/solid';
import { ApiErrorCode } from '@server/constants/error';
import { MediaServerType, ServerType } from '@server/constants/server';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { FormattedMessage, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -112,38 +113,19 @@ function JellyfinSetup({
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
// Check if serverType is either 'Jellyfin' or 'Emby'
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
// throw new Error('Invalid serverType'); // You can customize the error message
// }
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
serverType: serverType,
}),
await axios.post('/api/v1/auth/jellyfin', {
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
serverType: serverType,
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
let errorMessage = null;
switch (errorData?.message) {
switch (e?.response?.data?.message) {
case ApiErrorCode.InvalidUrl:
errorMessage = messages.invalidurlerror;
break;

View File

@@ -1,6 +1,7 @@
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -24,19 +25,9 @@ const LoginWithPlex = ({ onComplete }: LoginWithPlexProps) => {
useEffect(() => {
const login = async () => {
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
authToken,
}),
});
if (!res.ok) throw new Error();
const data = await res.json();
const response = await axios.post('/api/v1/auth/plex', { authToken });
if (data?.id) {
if (response.data?.id) {
revalidate();
}
};

View File

@@ -4,6 +4,7 @@ import JellyfinSetup from '@app/components/Setup/JellyfinSetup';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
@@ -40,19 +41,11 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
useEffect(() => {
const login = async () => {
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
authToken: authToken,
}),
const response = await axios.post('/api/v1/auth/plex', {
authToken: authToken,
});
if (!res.ok) throw new Error();
const data = await res.json();
if (data?.email) {
if (response.data?.email) {
revalidate();
}
};

View File

@@ -15,6 +15,7 @@ import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
import type { Library } from '@server/lib/settings';
import axios from 'axios';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useState } from 'react';
@@ -57,27 +58,15 @@ const Setup = () => {
const finishSetup = async () => {
setIsUpdating(true);
const res = await fetch('/api/v1/settings/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) throw new Error();
const data: { initialized: boolean } = await res.json();
const response = await axios.post<{ initialized: boolean }>(
'/api/v1/settings/initialize'
);
setIsUpdating(false);
if (data.initialized) {
const mainRes = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ locale }),
});
if (!mainRes.ok) throw new Error();
if (response.data.initialized) {
await axios.post('/api/v1/settings/main', { locale });
mutate('/api/v1/settings/public');
router.push('/');
}
};
@@ -94,11 +83,9 @@ const Setup = () => {
const endpoint = endpointMap[mediaServerType];
if (!endpoint) return;
const res = await fetch(endpoint);
if (!res.ok) throw new Error('Fetch failed');
const data = await res.json();
const response = await axios.get(endpoint);
const hasEnabledLibraries = data?.libraries?.some(
const hasEnabledLibraries = response.data?.libraries?.some(
(library: Library) => library.enabled
);

View File

@@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { CheckIcon, TrashIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { useIntl } from 'react-intl';
import { mutate } from 'swr';
@@ -24,10 +25,7 @@ const ErrorCard = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
const intl = useIntl();
const deleteMedia = async () => {
const res = await fetch(`/api/v1/media/${id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/media/${id}`);
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
};

View File

@@ -23,6 +23,7 @@ import {
import { MediaStatus } from '@server/constants/media';
import type { Watchlist } from '@server/entity/Watchlist';
import type { MediaType } from '@server/models/Search';
import axios from 'axios';
import Link from 'next/link';
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -108,21 +109,13 @@ const TitleCard = ({
const onClickWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const res = await fetch('/api/v1/watchlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tmdbId: id,
mediaType,
title,
}),
const response = await axios.post<Watchlist>('/api/v1/watchlist', {
tmdbId: id,
mediaType,
title,
});
if (!res.ok) throw new Error();
const data: Watchlist = await res.json();
mutate('/api/v1/discover/watchlist');
if (data) {
if (response.data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
@@ -147,11 +140,9 @@ const TitleCard = ({
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const res = await fetch('/api/v1/watchlist/' + id, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
if (res.status === 204) {
const response = await axios.delete<Watchlist>('/api/v1/watchlist/' + id);
if (response.status === 204) {
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {
@@ -182,21 +173,13 @@ const TitleCard = ({
const topNode = cardRef.current;
if (topNode) {
const res = await fetch('/api/v1/blacklist', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
try {
await axios.post('/api/v1/blacklist', {
tmdbId: id,
mediaType,
title,
user: user?.id,
}),
});
if (res.status === 201) {
});
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
@@ -207,21 +190,23 @@ const TitleCard = ({
{ appearance: 'success', autoDismiss: true }
);
setCurrentStatus(MediaStatus.BLACKLISTED);
} else if (res.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
} catch (e) {
if (e?.response?.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
}
}
setIsUpdating(false);
@@ -239,9 +224,7 @@ const TitleCard = ({
const topNode = cardRef.current;
if (topNode) {
const res = await fetch('/api/v1/blacklist/' + id, {
method: 'DELETE',
});
const res = await axios.delete('/api/v1/blacklist/' + id);
if (res.status === 204) {
addToast(

View File

@@ -57,6 +57,7 @@ import {
import { MediaServerType } from '@server/constants/server';
import type { Crew } from '@server/models/common';
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
import axios from 'axios';
import { countries } from 'country-flag-icons';
import 'country-flag-icons/3x2/flags.css';
import Link from 'next/link';
@@ -349,32 +350,12 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
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 {
await axios.post('/api/v1/watchlist', {
tmdbId: tv?.id,
mediaType: MediaType.TV,
title: tv?.name,
}),
});
if (!res.ok) {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
});
setIsUpdating(false);
return;
}
const data = await res.json();
if (data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
@@ -384,30 +365,25 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</span>,
{ appearance: 'success', autoDismiss: true }
);
}
setIsUpdating(false);
setToggleWatchlist((prevState) => !prevState);
};
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
const res = await fetch('/api/v1/watchlist/' + tv?.id, {
method: 'DELETE',
});
if (!res.ok) {
setIsUpdating(false);
setToggleWatchlist((prevState) => !prevState);
} catch {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
});
setIsUpdating(false);
return;
}
};
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
await axios.delete('/api/v1/watchlist/' + tv?.id);
if (res.status === 204) {
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {
@@ -417,55 +393,60 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
</span>,
{ appearance: 'info', autoDismiss: true }
);
setIsUpdating(false);
setToggleWatchlist((prevState) => !prevState);
} catch {
addToast(intl.formatMessage(messages.watchlistError), {
appearance: 'error',
autoDismiss: true,
});
setIsUpdating(false);
}
};
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 res = await axios.post('/api/v1/blacklist', {
tmdbId: tv?.id,
mediaType: 'tv',
title: tv?.name,
user: user?.id,
}),
});
if (res.status === 201) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
title: tv?.name,
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: tv?.name,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
if (res.status === 201) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistSuccess, {
title: tv?.name,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'success', autoDismiss: true }
);
revalidate();
}
} catch (e) {
if (e?.response?.status === 412) {
addToast(
<span>
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
title: tv?.name,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</span>,
{ appearance: 'info', autoDismiss: true }
);
} else {
addToast(intl.formatMessage(globalMessages.blacklistError), {
appearance: 'error',
autoDismiss: true,
});
}
}
setIsBlacklistUpdating(false);

View File

@@ -5,6 +5,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { hasPermission } from '@server/lib/permissions';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -45,18 +46,10 @@ const BulkEditModal = ({
const updateUsers = async () => {
try {
setIsSaving(true);
const res = await fetch('/api/v1/user', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ids: selectedUserIds,
permissions: currentPermission,
}),
const { data: updated } = await axios.put<User[]>(`/api/v1/user`, {
ids: selectedUserIds,
permissions: currentPermission,
});
if (!res.ok) throw new Error();
const updated: User[] = await res.json();
if (onComplete) {
onComplete(updated);
}

View File

@@ -6,6 +6,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
import axios from 'axios';
import { useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -59,17 +60,10 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
setImporting(true);
try {
const res = await fetch('/api/v1/user/import-from-jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jellyfinUserIds: selectedUsers,
}),
});
if (!res.ok) throw new Error();
const createdUsers = await res.json();
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-jellyfin',
{ jellyfinUserIds: selectedUsers }
);
if (!createdUsers.length) {
throw new Error('No users were imported from Jellyfin.');

View File

@@ -3,6 +3,7 @@ import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import Image from 'next/image';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -47,17 +48,10 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
setImporting(true);
try {
const res = await fetch('/api/v1/user/import-from-plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
plexIds: selectedUsers,
}),
});
if (!res.ok) throw new Error();
const createdUsers = await res.json();
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-plex',
{ plexIds: selectedUsers }
);
if (!Array.isArray(createdUsers) || createdUsers.length === 0) {
throw new Error('No users were imported from Plex.');

View File

@@ -28,6 +28,7 @@ import {
import { MediaServerType } from '@server/constants/server';
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
import { hasPermission } from '@server/lib/permissions';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -181,10 +182,7 @@ const UserList = () => {
setDeleting(true);
try {
const res = await fetch(`/api/v1/user/${deleteModal.user?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
await axios.delete(`/api/v1/user/${deleteModal.user?.id}`);
addToast(intl.formatMessage(messages.userdeleted), {
autoDismiss: true,
@@ -286,34 +284,20 @@ const UserList = () => {
validationSchema={CreateUserSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
email: values.email,
password: values.genpassword ? null : values.password,
}),
await axios.post('/api/v1/user', {
username: values.username,
email: values.email,
password: values.genpassword ? null : values.password,
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
addToast(intl.formatMessage(messages.usercreatedsuccess), {
appearance: 'success',
autoDismiss: true,
});
setCreateModal({ isOpen: false });
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
addToast(
intl.formatMessage(
errorData.errors?.includes('USER_EXISTS')
e?.response?.data?.errors?.includes('USER_EXISTS')
? messages.usercreatedfailedexisting
: messages.usercreatedfailed
),

View File

@@ -16,6 +16,7 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { ApiErrorCode } from '@server/constants/error';
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -164,33 +165,24 @@ const UserGeneralSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(`/api/v1/user/${user?.id}/settings/main`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.displayName,
email:
values.email || user?.jellyfinUsername || user?.plexUsername,
discordId: values.discordId,
locale: values.locale,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
movieQuotaLimit: movieQuotaEnabled
? values.movieQuotaLimit
: null,
movieQuotaDays: movieQuotaEnabled
? values.movieQuotaDays
: null,
tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null,
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
}),
await axios.post(`/api/v1/user/${user?.id}/settings/main`, {
username: values.displayName,
email:
values.email || user?.jellyfinUsername || user?.plexUsername,
discordId: values.discordId,
locale: values.locale,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
movieQuotaLimit: movieQuotaEnabled
? values.movieQuotaLimit
: null,
movieQuotaDays: movieQuotaEnabled ? values.movieQuotaDays : null,
tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null,
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
});
if (!res.ok) throw new Error(res.statusText, { cause: res });
if (currentUser?.id === user?.id && setLocale) {
setLocale(
@@ -205,14 +197,7 @@ const UserGeneralSettings = () => {
appearance: 'success',
});
} catch (e) {
let errorData;
try {
errorData = await e.cause?.text();
errorData = JSON.parse(errorData);
} catch {
/* empty */
}
if (errorData?.message === ApiErrorCode.InvalidEmail) {
if (e?.response?.data?.message === ApiErrorCode.InvalidEmail) {
if (values.email) {
addToast(
intl.formatMessage(messages.toastSettingsFailureEmail),

View File

@@ -5,6 +5,7 @@ import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -80,38 +81,28 @@ const LinkJellyfinModal: React.FC<LinkJellyfinModalProps> = ({
onSubmit={async ({ username, password }) => {
try {
setError(null);
const res = await fetch(
await axios.post(
`/api/v1/user/${user?.id}/settings/linked-accounts/jellyfin`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
}),
username,
password,
}
);
if (!res.ok) {
if (res.status === 401) {
setError(
intl.formatMessage(messages.errorUnauthorized, {
mediaServerName,
})
);
} else if (res.status === 422) {
setError(
intl.formatMessage(messages.errorExists, { applicationName })
);
} else {
setError(intl.formatMessage(messages.errorUnknown));
}
} else {
onSave();
}
onSave();
} catch (e) {
setError(intl.formatMessage(messages.errorUnknown));
if (e?.response?.status === 401) {
setError(
intl.formatMessage(messages.errorUnauthorized, {
mediaServerName,
})
);
} else if (e?.response?.status === 422) {
setError(
intl.formatMessage(messages.errorExists, { applicationName })
);
} else {
setError(intl.formatMessage(messages.errorUnknown));
}
}
}}
>

View File

@@ -12,6 +12,7 @@ import defineMessages from '@app/utils/defineMessages';
import PlexOAuth from '@app/utils/plex';
import { TrashIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -91,25 +92,21 @@ const UserLinkedAccountsSettings = () => {
setError(null);
try {
const authToken = await plexOAuth.login();
const res = await fetch(
await axios.post(
`/api/v1/user/${user?.id}/settings/linked-accounts/plex`,
{
method: 'POST',
body: JSON.stringify({ authToken }),
authToken,
}
);
if (!res.ok) {
if (res.status === 401) {
setError(intl.formatMessage(messages.plexErrorUnauthorized));
} else if (res.status === 422) {
setError(intl.formatMessage(messages.plexErrorExists));
} else {
setError(intl.formatMessage(messages.errorUnknown));
}
} else {
await revalidateUser();
}
await revalidateUser();
} catch (e) {
if (e?.response?.status === 401) {
setError(intl.formatMessage(messages.plexErrorUnauthorized));
} else if (e?.response?.status === 422) {
setError(intl.formatMessage(messages.plexErrorExists));
} else {
setError(intl.formatMessage(messages.errorUnknown));
}
setError(intl.formatMessage(messages.errorUnknown));
}
};
@@ -143,11 +140,9 @@ const UserLinkedAccountsSettings = () => {
const deleteRequest = async (account: string) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/linked-accounts/${account}`,
{ method: 'DELETE' }
await axios.delete(
`/api/v1/user/${user?.id}/settings/linked-accounts/${account}`
);
if (!res.ok) throw new Error();
} catch {
setError(intl.formatMessage(messages.deleteFailed));
}

View File

@@ -6,6 +6,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -67,28 +68,18 @@ const UserNotificationsDiscord = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: values.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
discord: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: values.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
discord: values.types,
},
});
addToast(intl.formatMessage(messages.discordsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -11,6 +11,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -66,28 +67,18 @@ const UserEmailSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: values.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
email: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: values.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
email: values.types,
},
});
addToast(intl.formatMessage(messages.emailsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -6,6 +6,7 @@ import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -64,28 +65,18 @@ const UserPushbulletSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: values.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushbullet: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: values.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushbullet: values.types,
},
});
addToast(intl.formatMessage(messages.pushbulletsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -7,6 +7,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import type { PushoverSound } from '@server/api/pushover';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -96,28 +97,18 @@ const UserPushoverSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: values.pushoverApplicationToken,
pushoverUserKey: values.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushover: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: values.pushoverApplicationToken,
pushoverUserKey: values.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushover: values.types,
},
});
addToast(intl.formatMessage(messages.pushoversettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -6,6 +6,7 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -90,29 +91,19 @@ const UserTelegramSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: values.telegramChatId,
telegramMessageThreadId: values.telegramMessageThreadId,
telegramSendSilently: values.telegramSendSilently,
notificationTypes: {
telegram: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: values.telegramChatId,
telegramMessageThreadId: values.telegramMessageThreadId,
telegramSendSilently: values.telegramSendSilently,
notificationTypes: {
telegram: values.types,
},
});
addToast(intl.formatMessage(messages.telegramsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -16,6 +16,7 @@ import {
} from '@heroicons/react/24/solid';
import type { UserPushSubscription } from '@server/entity/UserPushSubscription';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -84,21 +85,12 @@ const UserWebPushSettings = () => {
const parsedSub = JSON.parse(JSON.stringify(sub));
if (parsedSub.keys.p256dh && parsedSub.keys.auth) {
const res = await fetch('/api/v1/user/registerPushSubscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
endpoint: parsedSub.endpoint,
p256dh: parsedSub.keys.p256dh,
auth: parsedSub.keys.auth,
userAgent: navigator.userAgent,
}),
await axios.post('/api/v1/user/registerPushSubscription', {
endpoint: parsedSub.endpoint,
p256dh: parsedSub.keys.p256dh,
auth: parsedSub.keys.auth,
userAgent: navigator.userAgent,
});
if (!res.ok) {
throw new Error(res.statusText);
}
setWebPushEnabled(true);
addToast(intl.formatMessage(messages.webpushhasbeenenabled), {
appearance: 'success',
@@ -129,17 +121,11 @@ const UserWebPushSettings = () => {
.then(async (subscription) => {
const parsedSub = JSON.parse(JSON.stringify(subscription));
const res = await fetch(
await axios.delete(
`/api/v1/user/${user?.id}/pushSubscription/${
p256dh ? p256dh : parsedSub.keys.p256dh
}`,
{
method: 'DELETE',
}
}`
);
if (!res.ok) {
throw new Error(res.statusText);
}
if (subscription && (p256dh === parsedSub.keys.p256dh || !p256dh)) {
subscription.unsubscribe();
setWebPushEnabled(false);
@@ -188,17 +174,10 @@ const UserWebPushSettings = () => {
.then(async (subscription) => {
if (subscription) {
const parsedKey = JSON.parse(JSON.stringify(subscription));
const response = await fetch(
`/api/v1/user/${user.id}/pushSubscription/${parsedKey.keys.p256dh}`
);
if (!response.ok) {
throw new Error(response.statusText);
}
const currentUserPushSub = {
data: (await response.json()) as UserPushSubscription,
};
const currentUserPushSub =
await axios.get<UserPushSubscription>(
`/api/v1/user/${user.id}/pushSubscription/${parsedKey.keys.p256dh}`
);
if (currentUserPushSub.data.p256dh !== parsedKey.keys.p256dh) {
return;
@@ -233,30 +212,21 @@ const UserWebPushSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
await axios.post(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
webpush: values.types,
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
webpush: values.types,
},
}),
}
);
if (!res.ok) {
throw new Error(res.statusText);
}
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.webpushsettingssaved), {
appearance: 'success',

View File

@@ -8,6 +8,7 @@ import globalMessages from '@app/i18n/globalMessages';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -122,21 +123,11 @@ const UserPasswordChange = () => {
enableReinitialize
onSubmit={async (values, { resetForm }) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/password`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
currentPassword: values.currentPassword,
newPassword: values.newPassword,
confirmPassword: values.confirmPassword,
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/password`, {
currentPassword: values.currentPassword,
newPassword: values.newPassword,
confirmPassword: values.confirmPassword,
});
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,

View File

@@ -8,6 +8,7 @@ import globalMessages from '@app/i18n/globalMessages';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -83,19 +84,10 @@ const UserPermissions = () => {
enableReinitialize
onSubmit={async (values) => {
try {
const res = await fetch(
`/api/v1/user/${user?.id}/settings/permissions`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: values.currentPermissions ?? 0,
}),
}
);
if (!res.ok) throw new Error();
await axios.post(`/api/v1/user/${user?.id}/settings/permissions`, {
permissions: values.currentPermissions ?? 0,
});
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,
appearance: 'success',