mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(ui): Add 'Available' filter to request list and remove unused MediaRequestStatus.AVAILABLE enum value (#905)
This commit is contained in:
@@ -27,13 +27,13 @@ tags:
|
|||||||
- name: tv
|
- name: tv
|
||||||
description: Endpoints related to retrieving TV series and their details.
|
description: Endpoints related to retrieving TV series and their details.
|
||||||
- name: person
|
- name: person
|
||||||
description: Endpoints related to retrieving Person details.
|
description: Endpoints related to retrieving person details.
|
||||||
- name: media
|
- name: media
|
||||||
description: Endpoints related to media management.
|
description: Endpoints related to media management.
|
||||||
- name: collection
|
- name: collection
|
||||||
description: Endpoints related to retrieving Collection details.
|
description: Endpoints related to retrieving collection details.
|
||||||
- name: service
|
- name: service
|
||||||
description: Endpoinst related to getting Service (Radarr/Sonarr) details.
|
description: Endpoints related to getting service (Radarr/Sonarr) details.
|
||||||
servers:
|
servers:
|
||||||
- url: '{server}/api/v1'
|
- url: '{server}/api/v1'
|
||||||
variables:
|
variables:
|
||||||
@@ -3095,7 +3095,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
enum: [all, available, approved, pending, unavailable]
|
enum: [all, approved, available, pending, processing, unavailable]
|
||||||
- in: query
|
- in: query
|
||||||
name: sort
|
name: sort
|
||||||
schema:
|
schema:
|
||||||
@@ -3187,6 +3187,12 @@ paths:
|
|||||||
approved:
|
approved:
|
||||||
type: number
|
type: number
|
||||||
example: 10
|
example: 10
|
||||||
|
processing:
|
||||||
|
type: number
|
||||||
|
example: 4
|
||||||
|
available:
|
||||||
|
type: number
|
||||||
|
example: 6
|
||||||
required:
|
required:
|
||||||
- pending
|
- pending
|
||||||
- approved
|
- approved
|
||||||
|
@@ -2,7 +2,6 @@ export enum MediaRequestStatus {
|
|||||||
PENDING = 1,
|
PENDING = 1,
|
||||||
APPROVED,
|
APPROVED,
|
||||||
DECLINED,
|
DECLINED,
|
||||||
AVAILABLE,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MediaType {
|
export enum MediaType {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { isAuthenticated } from '../middleware/auth';
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm';
|
import { getRepository } from 'typeorm';
|
||||||
import { MediaRequest } from '../entity/MediaRequest';
|
import { MediaRequest } from '../entity/MediaRequest';
|
||||||
import TheMovieDb from '../api/themoviedb';
|
import TheMovieDb from '../api/themoviedb';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
@@ -19,61 +19,98 @@ requestRoutes.get('/', async (req, res, next) => {
|
|||||||
const pageSize = req.query.take ? Number(req.query.take) : 20;
|
const pageSize = req.query.take ? Number(req.query.take) : 20;
|
||||||
const skip = req.query.skip ? Number(req.query.skip) : 0;
|
const skip = req.query.skip ? Number(req.query.skip) : 0;
|
||||||
|
|
||||||
let statusFilter:
|
let statusFilter: MediaRequestStatus[];
|
||||||
| MediaRequestStatus
|
|
||||||
| FindOperator<string | MediaRequestStatus>
|
switch (req.query.filter) {
|
||||||
| undefined = undefined;
|
case 'approved':
|
||||||
|
case 'processing':
|
||||||
|
case 'available':
|
||||||
|
statusFilter = [MediaRequestStatus.APPROVED];
|
||||||
|
break;
|
||||||
|
case 'pending':
|
||||||
|
statusFilter = [MediaRequestStatus.PENDING];
|
||||||
|
break;
|
||||||
|
case 'unavailable':
|
||||||
|
statusFilter = [
|
||||||
|
MediaRequestStatus.PENDING,
|
||||||
|
MediaRequestStatus.APPROVED,
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusFilter = [
|
||||||
|
MediaRequestStatus.PENDING,
|
||||||
|
MediaRequestStatus.APPROVED,
|
||||||
|
MediaRequestStatus.DECLINED,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaStatusFilter: MediaStatus[];
|
||||||
|
|
||||||
switch (req.query.filter) {
|
switch (req.query.filter) {
|
||||||
case 'available':
|
case 'available':
|
||||||
statusFilter = MediaRequestStatus.AVAILABLE;
|
mediaStatusFilter = [MediaStatus.AVAILABLE];
|
||||||
break;
|
|
||||||
case 'approved':
|
|
||||||
statusFilter = MediaRequestStatus.APPROVED;
|
|
||||||
break;
|
|
||||||
case 'pending':
|
|
||||||
statusFilter = MediaRequestStatus.PENDING;
|
|
||||||
break;
|
break;
|
||||||
|
case 'processing':
|
||||||
case 'unavailable':
|
case 'unavailable':
|
||||||
statusFilter = In([
|
mediaStatusFilter = [
|
||||||
MediaRequestStatus.PENDING,
|
MediaStatus.UNKNOWN,
|
||||||
MediaRequestStatus.APPROVED,
|
MediaStatus.PENDING,
|
||||||
]);
|
MediaStatus.PROCESSING,
|
||||||
|
MediaStatus.PARTIALLY_AVAILABLE,
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
statusFilter = In(Object.values(MediaRequestStatus));
|
mediaStatusFilter = [
|
||||||
|
MediaStatus.UNKNOWN,
|
||||||
|
MediaStatus.PENDING,
|
||||||
|
MediaStatus.PROCESSING,
|
||||||
|
MediaStatus.PARTIALLY_AVAILABLE,
|
||||||
|
MediaStatus.AVAILABLE,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortFilter: FindOneOptions<MediaRequest>['order'] = {
|
let sortFilter: string;
|
||||||
id: 'DESC',
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (req.query.sort) {
|
switch (req.query.sort) {
|
||||||
case 'modified':
|
case 'modified':
|
||||||
sortFilter = {
|
sortFilter = 'request.updatedAt';
|
||||||
updatedAt: 'DESC',
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
sortFilter = 'request.id';
|
||||||
}
|
}
|
||||||
|
|
||||||
const [requests, requestCount] = req.user?.hasPermission(
|
let query = requestRepository
|
||||||
|
.createQueryBuilder('request')
|
||||||
|
.leftJoinAndSelect('request.media', 'media')
|
||||||
|
.leftJoinAndSelect('request.seasons', 'seasons')
|
||||||
|
.leftJoinAndSelect('request.modifiedBy', 'modifiedBy')
|
||||||
|
.leftJoinAndSelect('request.requestedBy', 'requestedBy')
|
||||||
|
.where('request.status IN (:...requestStatus)', {
|
||||||
|
requestStatus: statusFilter,
|
||||||
|
})
|
||||||
|
.andWhere(
|
||||||
|
'(request.is4k = false AND media.status IN (:...mediaStatus)) OR (request.is4k = true AND media.status4k IN (:...mediaStatus))',
|
||||||
|
{
|
||||||
|
mediaStatus: mediaStatusFilter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!req.user?.hasPermission(
|
||||||
[Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW],
|
[Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW],
|
||||||
{ type: 'or' }
|
{ type: 'or' }
|
||||||
)
|
)
|
||||||
? await requestRepository.findAndCount({
|
) {
|
||||||
order: sortFilter,
|
query = query.andWhere('request.requestedBy.id = :id', {
|
||||||
relations: ['media', 'modifiedBy'],
|
id: req.user?.id,
|
||||||
where: { status: statusFilter },
|
|
||||||
take: Number(req.query.take) ?? 20,
|
|
||||||
skip,
|
|
||||||
})
|
|
||||||
: await requestRepository.findAndCount({
|
|
||||||
where: { requestedBy: { id: req.user?.id }, status: statusFilter },
|
|
||||||
relations: ['media', 'modifiedBy'],
|
|
||||||
order: sortFilter,
|
|
||||||
take: Number(req.query.limit) ?? 20,
|
|
||||||
skip,
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [requests, requestCount] = await query
|
||||||
|
.orderBy(sortFilter, 'DESC')
|
||||||
|
.take(pageSize)
|
||||||
|
.skip(skip)
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
@@ -279,16 +316,51 @@ requestRoutes.get('/count', async (_req, res, next) => {
|
|||||||
const requestRepository = getRepository(MediaRequest);
|
const requestRepository = getRepository(MediaRequest);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pendingCount = await requestRepository.count({
|
const query = requestRepository
|
||||||
status: MediaRequestStatus.PENDING,
|
.createQueryBuilder('request')
|
||||||
});
|
.leftJoinAndSelect('request.media', 'media');
|
||||||
const approvedCount = await requestRepository.count({
|
|
||||||
status: MediaRequestStatus.APPROVED,
|
const pendingCount = await query
|
||||||
});
|
.where('request.status = :requestStatus', {
|
||||||
|
requestStatus: MediaRequestStatus.PENDING,
|
||||||
|
})
|
||||||
|
.getCount();
|
||||||
|
|
||||||
|
const approvedCount = await query
|
||||||
|
.where('request.status = :requestStatus', {
|
||||||
|
requestStatus: MediaRequestStatus.APPROVED,
|
||||||
|
})
|
||||||
|
.getCount();
|
||||||
|
|
||||||
|
const processingCount = await query
|
||||||
|
.where('request.status = :requestStatus', {
|
||||||
|
requestStatus: MediaRequestStatus.APPROVED,
|
||||||
|
})
|
||||||
|
.andWhere(
|
||||||
|
'(request.is4k = false AND media.status != :availableStatus) OR (request.is4k = true AND media.status4k != :availableStatus)',
|
||||||
|
{
|
||||||
|
availableStatus: MediaStatus.AVAILABLE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.getCount();
|
||||||
|
|
||||||
|
const availableCount = await query
|
||||||
|
.where('request.status = :requestStatus', {
|
||||||
|
requestStatus: MediaRequestStatus.APPROVED,
|
||||||
|
})
|
||||||
|
.andWhere(
|
||||||
|
'(request.is4k = false AND media.status = :availableStatus) OR (request.is4k = true AND media.status4k = :availableStatus)',
|
||||||
|
{
|
||||||
|
availableStatus: MediaStatus.AVAILABLE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.getCount();
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
pending: pendingCount,
|
pending: pendingCount,
|
||||||
approved: approvedCount,
|
approved: approvedCount,
|
||||||
|
processing: processingCount,
|
||||||
|
available: availableCount,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
next({ status: 500, message: e.message });
|
next({ status: 500, message: e.message });
|
||||||
|
@@ -22,13 +22,15 @@ const messages = defineMessages({
|
|||||||
filterAll: 'All',
|
filterAll: 'All',
|
||||||
filterPending: 'Pending',
|
filterPending: 'Pending',
|
||||||
filterApproved: 'Approved',
|
filterApproved: 'Approved',
|
||||||
|
filterAvailable: 'Available',
|
||||||
|
filterProcessing: 'Processing',
|
||||||
noresults: 'No results.',
|
noresults: 'No results.',
|
||||||
showallrequests: 'Show All Requests',
|
showallrequests: 'Show All Requests',
|
||||||
sortAdded: 'Request Date',
|
sortAdded: 'Request Date',
|
||||||
sortModified: 'Last Modified',
|
sortModified: 'Last Modified',
|
||||||
});
|
});
|
||||||
|
|
||||||
type Filter = 'all' | 'approved' | 'pending';
|
type Filter = 'all' | 'pending' | 'approved' | 'processing' | 'available';
|
||||||
type Sort = 'added' | 'modified';
|
type Sort = 'added' | 'modified';
|
||||||
|
|
||||||
const RequestList: React.FC = () => {
|
const RequestList: React.FC = () => {
|
||||||
@@ -93,6 +95,12 @@ const RequestList: React.FC = () => {
|
|||||||
<option value="approved">
|
<option value="approved">
|
||||||
{intl.formatMessage(messages.filterApproved)}
|
{intl.formatMessage(messages.filterApproved)}
|
||||||
</option>
|
</option>
|
||||||
|
<option value="processing">
|
||||||
|
{intl.formatMessage(messages.filterProcessing)}
|
||||||
|
</option>
|
||||||
|
<option value="available">
|
||||||
|
{intl.formatMessage(messages.filterAvailable)}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow mb-2 sm:mb-0 lg:flex-grow-0">
|
<div className="flex flex-grow mb-2 sm:mb-0 lg:flex-grow-0">
|
||||||
|
@@ -523,13 +523,6 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
{intl.formatMessage(globalMessages.requested)}
|
{intl.formatMessage(globalMessages.requested)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!mediaSeason &&
|
|
||||||
seasonRequest?.status ===
|
|
||||||
MediaRequestStatus.AVAILABLE && (
|
|
||||||
<Badge badgeType="success">
|
|
||||||
{intl.formatMessage(globalMessages.available)}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{mediaSeason?.[is4k ? 'status4k' : 'status'] ===
|
{mediaSeason?.[is4k ? 'status4k' : 'status'] ===
|
||||||
MediaStatus.PARTIALLY_AVAILABLE && (
|
MediaStatus.PARTIALLY_AVAILABLE && (
|
||||||
<Badge badgeType="success">
|
<Badge badgeType="success">
|
||||||
|
@@ -152,7 +152,9 @@
|
|||||||
"components.RequestList.RequestItem.seasons": "Seasons",
|
"components.RequestList.RequestItem.seasons": "Seasons",
|
||||||
"components.RequestList.filterAll": "All",
|
"components.RequestList.filterAll": "All",
|
||||||
"components.RequestList.filterApproved": "Approved",
|
"components.RequestList.filterApproved": "Approved",
|
||||||
|
"components.RequestList.filterAvailable": "Available",
|
||||||
"components.RequestList.filterPending": "Pending",
|
"components.RequestList.filterPending": "Pending",
|
||||||
|
"components.RequestList.filterProcessing": "Processing",
|
||||||
"components.RequestList.mediaInfo": "Media Info",
|
"components.RequestList.mediaInfo": "Media Info",
|
||||||
"components.RequestList.modifiedBy": "Last Modified By",
|
"components.RequestList.modifiedBy": "Last Modified By",
|
||||||
"components.RequestList.next": "Next",
|
"components.RequestList.next": "Next",
|
||||||
|
Reference in New Issue
Block a user