fix(api): Use POST instead of GET for API endpoints that mutate state (#877)

pull/892/head
TheCatLady 4 years ago committed by GitHub
parent d163e29459
commit ff0b5ed441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1537,7 +1537,7 @@ paths:
schema:
$ref: '#/components/schemas/MainSettings'
/settings/main/regenerate:
get:
post:
summary: Get main settings with newly-generated API key
description: Returns main settings in a JSON object, using the new API key.
tags:
@ -1612,21 +1612,50 @@ paths:
$ref: '#/components/schemas/PlexLibrary'
/settings/plex/sync:
get:
summary: Get status of full Plex library sync
description: Returns sync progress in a JSON array.
tags:
- settings
responses:
'200':
description: Status of Plex sync
content:
application/json:
schema:
type: object
properties:
running:
type: boolean
example: false
progress:
type: number
example: 0
total:
type: number
example: 100
currentLibrary:
$ref: '#/components/schemas/PlexLibrary'
libraries:
type: array
items:
$ref: '#/components/schemas/PlexLibrary'
post:
summary: Start full Plex library sync
description: Runs a full Plex library sync and returns the progress in a JSON array.
tags:
- settings
parameters:
- in: query
name: cancel
schema:
type: boolean
example: false
- in: query
name: start
schema:
type: boolean
example: false
requestBody:
content:
application/json:
schema:
type: object
properties:
cancel:
type: boolean
example: false
start:
type: boolean
example: false
responses:
'200':
description: Status of Plex sync
@ -1946,7 +1975,7 @@ paths:
schema:
$ref: '#/components/schemas/PublicSettings'
/settings/initialize:
get:
post:
summary: Initialize application
description: Sets the app as initialized, allowing the user to navigate to pages other than the setup page.
tags:
@ -1990,7 +2019,7 @@ paths:
type: boolean
example: false
/settings/jobs/{jobId}/run:
get:
post:
summary: Invoke a specific job
description: Invokes a specific job to run. Will return the new job status in JSON format.
tags:
@ -2025,7 +2054,7 @@ paths:
type: boolean
example: false
/settings/jobs/{jobId}/cancel:
get:
post:
summary: Cancel a specific job
description: Cancels a specific job. Will return the new job status in JSON format.
tags:
@ -2095,7 +2124,7 @@ paths:
vsize:
type: number
/settings/cache/{cacheId}/flush:
get:
post:
summary: Flush a specific cache
description: Flushes all data from the cache ID provided
tags:
@ -2511,7 +2540,7 @@ paths:
- email
- password
/auth/logout:
get:
post:
summary: Sign out and clear session cookie
description: Completely clear the session cookie and associated values, effectively signing the user out.
tags:
@ -3187,10 +3216,10 @@ paths:
schema:
$ref: '#/components/schemas/MediaRequest'
/request/{requestId}/{status}:
get:
summary: Update a requests status
post:
summary: Update a request's status
description: |
Updates a requests status to approved or declined. Also returns the request in a JSON object.
Updates a request's status to approved or declined. Also returns the request in a JSON object.
Requires the `MANAGE_REQUESTS` permission or `ADMIN`.
tags:
@ -3681,9 +3710,9 @@ paths:
'204':
description: Succesfully removed media item
/media/{mediaId}/{status}:
get:
post:
summary: Update media status
description: Updates a medias status and returns the media in JSON format
description: Updates a media item's status and returns the media in JSON format
tags:
- media
parameters:
@ -3702,12 +3731,15 @@ paths:
schema:
type: string
enum: [available, partial, processing, pending, unknown]
- in: query
name: is4k
description: 4K Status
example: false
schema:
type: boolean
requestBody:
content:
application/json:
schema:
type: object
properties:
is4k:
type: boolean
example: false
responses:
'200':
description: Returned media

@ -184,7 +184,7 @@ authRoutes.post('/local', async (req, res, next) => {
}
});
authRoutes.get('/logout', (req, res, next) => {
authRoutes.post('/logout', (req, res, next) => {
req.session?.destroy((err) => {
if (err) {
return next({

@ -82,7 +82,7 @@ mediaRoutes.get('/', async (req, res, next) => {
}
});
mediaRoutes.get<
mediaRoutes.post<
{
id: string;
status: 'available' | 'partial' | 'processing' | 'pending' | 'unknown';
@ -102,7 +102,7 @@ mediaRoutes.get<
return next({ status: 404, message: 'Media does not exist.' });
}
const is4k = Boolean(req.query.is4k);
const is4k = Boolean(req.body.is4k);
switch (req.params.status) {
case 'available':

@ -489,7 +489,7 @@ requestRoutes.post<{
}
);
requestRoutes.get<{
requestRoutes.post<{
requestId: string;
status: 'pending' | 'approve' | 'decline';
}>(

@ -54,7 +54,7 @@ settingsRoutes.post('/main', (req, res) => {
return res.status(200).json(settings.main);
});
settingsRoutes.get('/main/regenerate', (req, res, next) => {
settingsRoutes.post('/main/regenerate', (req, res, next) => {
const settings = getSettings();
const main = settings.regenerateApiKey();
@ -210,10 +210,14 @@ settingsRoutes.get('/plex/library', async (req, res) => {
return res.status(200).json(settings.plex.libraries);
});
settingsRoutes.get('/plex/sync', (req, res) => {
if (req.query.cancel) {
settingsRoutes.get('/plex/sync', (_req, res) => {
return res.status(200).json(jobPlexFullSync.status());
});
settingsRoutes.post('/plex/sync', (req, res) => {
if (req.body.cancel) {
jobPlexFullSync.cancel();
} else if (req.query.start) {
} else if (req.body.start) {
jobPlexFullSync.run();
}
return res.status(200).json(jobPlexFullSync.status());
@ -231,7 +235,7 @@ settingsRoutes.get('/jobs', (_req, res) => {
);
});
settingsRoutes.get<{ jobId: string }>('/jobs/:jobId/run', (req, res, next) => {
settingsRoutes.post<{ jobId: string }>('/jobs/:jobId/run', (req, res, next) => {
const scheduledJob = scheduledJobs.find((job) => job.id === req.params.jobId);
if (!scheduledJob) {
@ -249,7 +253,7 @@ settingsRoutes.get<{ jobId: string }>('/jobs/:jobId/run', (req, res, next) => {
});
});
settingsRoutes.get<{ jobId: string }>(
settingsRoutes.post<{ jobId: string }>(
'/jobs/:jobId/cancel',
(req, res, next) => {
const scheduledJob = scheduledJobs.find(
@ -286,7 +290,7 @@ settingsRoutes.get('/cache', (req, res) => {
);
});
settingsRoutes.get<{ cacheId: AvailableCacheIds }>(
settingsRoutes.post<{ cacheId: AvailableCacheIds }>(
'/cache/:cacheId/flush',
(req, res, next) => {
const cache = cacheManager.getCache(req.params.cacheId);
@ -300,7 +304,7 @@ settingsRoutes.get<{ cacheId: AvailableCacheIds }>(
}
);
settingsRoutes.get(
settingsRoutes.post(
'/initialize',
isAuthenticated(Permission.ADMIN),
(_req, res) => {

@ -16,7 +16,7 @@ const UserDropdown: React.FC = () => {
useClickOutside(dropdownRef, () => setDropdownOpen(false));
const logout = async () => {
const response = await axios.get('/api/v1/auth/logout');
const response = await axios.post('/api/v1/auth/logout');
if (response.data?.status === 'ok') {
revalidate();
@ -24,16 +24,16 @@ const UserDropdown: React.FC = () => {
};
return (
<div className="ml-3 relative">
<div className="relative ml-3">
<div>
<button
className="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:ring"
className="flex items-center max-w-xs text-sm rounded-full focus:outline-none focus:ring"
id="user-menu"
aria-label="User menu"
aria-haspopup="true"
onClick={() => setDropdownOpen(true)}
>
<img className="h-8 w-8 rounded-full" src={user?.avatar} alt="" />
<img className="w-8 h-8 rounded-full" src={user?.avatar} alt="" />
</button>
</div>
<Transition
@ -46,18 +46,18 @@ const UserDropdown: React.FC = () => {
leaveTo="transform opacity-0 scale-95"
>
<div
className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg"
className="absolute right-0 w-48 mt-2 origin-top-right rounded-md shadow-lg"
ref={dropdownRef}
>
<div
className="py-1 rounded-md bg-gray-700 ring-1 ring-black ring-opacity-5"
className="py-1 bg-gray-700 rounded-md ring-1 ring-black ring-opacity-5"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu"
>
<a
href="#"
className="block px-4 py-2 text-sm text-gray-200 hover:bg-gray-600 transition ease-in-out duration-150"
className="block px-4 py-2 text-sm text-gray-200 transition duration-150 ease-in-out hover:bg-gray-600"
role="menuitem"
onClick={() => logout()}
>

@ -123,10 +123,8 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
};
const markAvailable = async (is4k = false) => {
await axios.get(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
params: {
is4k,
},
await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
is4k,
});
revalidate();
};

@ -30,7 +30,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
const updateRequest = async (type: 'approve' | 'decline'): Promise<void> => {
setIsUpdating(true);
await axios.get(`/api/v1/request/${request.id}/${type}`);
await axios.post(`/api/v1/request/${request.id}/${type}`);
if (onUpdate) {
onUpdate();

@ -83,7 +83,7 @@ const RequestButton: React.FC<RequestButtonProps> = ({
request: MediaRequest,
type: 'approve' | 'decline'
) => {
const response = await axios.get(`/api/v1/request/${request.id}/${type}`);
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (response) {
onUpdate();
@ -100,7 +100,7 @@ const RequestButton: React.FC<RequestButtonProps> = ({
await Promise.all(
requests.map(async (request) => {
return axios.get(`/api/v1/request/${request.id}/${type}`);
return axios.post(`/api/v1/request/${request.id}/${type}`);
})
);

@ -62,7 +62,7 @@ const RequestCard: React.FC<RequestCardProps> = ({ request }) => {
});
const modifyRequest = async (type: 'approve' | 'decline') => {
const response = await axios.get(`/api/v1/request/${request.id}/${type}`);
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (response) {
revalidate();

@ -70,7 +70,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
const [isRetrying, setRetrying] = useState(false);
const modifyRequest = async (type: 'approve' | 'decline') => {
const response = await axios.get(`/api/v1/request/${request.id}/${type}`);
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
if (response) {
revalidate();

@ -63,7 +63,7 @@ const SettingsJobs: React.FC = () => {
}
const runJob = async (job: Job) => {
await axios.get(`/api/v1/settings/jobs/${job.id}/run`);
await axios.post(`/api/v1/settings/jobs/${job.id}/run`);
addToast(
intl.formatMessage(messages.jobstarted, {
jobname: job.name,
@ -77,7 +77,7 @@ const SettingsJobs: React.FC = () => {
};
const cancelJob = async (job: Job) => {
await axios.get(`/api/v1/settings/jobs/${job.id}/cancel`);
await axios.post(`/api/v1/settings/jobs/${job.id}/cancel`);
addToast(intl.formatMessage(messages.jobcancelled, { jobname: job.name }), {
appearance: 'error',
autoDismiss: true,
@ -86,7 +86,7 @@ const SettingsJobs: React.FC = () => {
};
const flushCache = async (cache: CacheItem) => {
await axios.get(`/api/v1/settings/cache/${cache.id}/flush`);
await axios.post(`/api/v1/settings/cache/${cache.id}/flush`);
addToast(
intl.formatMessage(messages.cacheflushed, { cachename: cache.name }),
{

@ -70,7 +70,7 @@ const SettingsMain: React.FC = () => {
const regenerate = async () => {
try {
await axios.get('/api/v1/settings/main/regenerate');
await axios.post('/api/v1/settings/main/regenerate');
revalidate();
addToast(intl.formatMessage(messages.toastApiKeySuccess), {

@ -212,19 +212,15 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
};
const startScan = async () => {
await axios.get('/api/v1/settings/plex/sync', {
params: {
start: true,
},
await axios.post('/api/v1/settings/plex/sync', {
start: true,
});
revalidateSync();
};
const cancelScan = async () => {
await axios.get('/api/v1/settings/plex/sync', {
params: {
cancel: true,
},
await axios.post('/api/v1/settings/plex/sync', {
cancel: true,
});
revalidateSync();
};

@ -35,7 +35,7 @@ const Setup: React.FC = () => {
const finishSetup = async () => {
setIsUpdating(false);
const response = await axios.get<{ initialized: boolean }>(
const response = await axios.post<{ initialized: boolean }>(
'/api/v1/settings/initialize'
);

@ -127,10 +127,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
};
const markAvailable = async (is4k = false) => {
await axios.get(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
params: {
is4k,
},
await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
is4k,
});
revalidate();
};

Loading…
Cancel
Save