diff --git a/overseerr-api.yml b/overseerr-api.yml index b06eb16a7..fdcd9bc5c 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1070,9 +1070,6 @@ components: pages: type: number example: 10 - pageSize: - type: number - example: 10 results: type: number example: 100 @@ -2747,10 +2744,22 @@ paths: /user: get: summary: Get all users - description: Returns all users in a JSON array. + description: Returns all users in a JSON object. tags: - users parameters: + - in: query + name: take + schema: + type: number + nullable: true + example: 20 + - in: query + name: skip + schema: + type: number + nullable: true + example: 0 - in: query name: sort schema: @@ -2763,9 +2772,14 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/User' + type: object + properties: + pageInfo: + $ref: '#/components/schemas/PageInfo' + results: + type: array + items: + $ref: '#/components/schemas/User' post: summary: Create new user description: | diff --git a/server/interfaces/api/userInterfaces.ts b/server/interfaces/api/userInterfaces.ts index dc87c76d6..259455dc9 100644 --- a/server/interfaces/api/userInterfaces.ts +++ b/server/interfaces/api/userInterfaces.ts @@ -1,6 +1,11 @@ +import type { User } from '../../entity/User'; import { MediaRequest } from '../../entity/MediaRequest'; import { PaginatedResponse } from './common'; +export interface UserResultsResponse extends PaginatedResponse { + results: User[]; +} + export interface UserRequestsResponse extends PaginatedResponse { results: MediaRequest[]; } diff --git a/server/routes/request.ts b/server/routes/request.ts index 6c362d91f..ec89a9831 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -14,9 +14,8 @@ import { User } from '../entity/User'; const requestRoutes = Router(); requestRoutes.get('/', async (req, res, next) => { - const requestRepository = getRepository(MediaRequest); try { - const pageSize = req.query.take ? Number(req.query.take) : 20; + const pageSize = req.query.take ? Number(req.query.take) : 10; const skip = req.query.skip ? Number(req.query.skip) : 0; let statusFilter: MediaRequestStatus[]; @@ -79,7 +78,7 @@ requestRoutes.get('/', async (req, res, next) => { sortFilter = 'request.id'; } - let query = requestRepository + let query = getRepository(MediaRequest) .createQueryBuilder('request') .leftJoinAndSelect('request.media', 'media') .leftJoinAndSelect('request.seasons', 'seasons') diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index ce9eb722a..9ea3f2c5b 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -9,46 +9,63 @@ import logger from '../../logger'; import gravatarUrl from 'gravatar-url'; import { UserType } from '../../constants/user'; import { isAuthenticated } from '../../middleware/auth'; +import { UserResultsResponse } from '../../interfaces/api/userInterfaces'; import { UserRequestsResponse } from '../../interfaces/api/userInterfaces'; import userSettingsRoutes from './usersettings'; const router = Router(); -router.get('/', async (req, res) => { - let query = getRepository(User).createQueryBuilder('user'); - - switch (req.query.sort) { - case 'updated': - query = query.orderBy('user.updatedAt', 'DESC'); - break; - case 'displayname': - query = query.orderBy( - '(CASE WHEN user.username IS NULL THEN user.plexUsername ELSE user.username END)', - 'ASC' - ); - break; - case 'requests': - query = query - .addSelect((subQuery) => { - return subQuery - .select('COUNT(request.id)', 'requestCount') - .from(MediaRequest, 'request') - .where('request.requestedBy.id = user.id'); - }, 'requestCount') - .orderBy('requestCount', 'DESC'); - break; - default: - query = query.orderBy('user.id', 'ASC'); - break; - } - - const users = await query.getMany(); +router.get('/', async (req, res, next) => { + try { + const pageSize = req.query.take ? Number(req.query.take) : 10; + const skip = req.query.skip ? Number(req.query.skip) : 0; + let query = getRepository(User).createQueryBuilder('user'); + + switch (req.query.sort) { + case 'updated': + query = query.orderBy('user.updatedAt', 'DESC'); + break; + case 'displayname': + query = query.orderBy( + '(CASE WHEN user.username IS NULL THEN user.plexUsername ELSE user.username END)', + 'ASC' + ); + break; + case 'requests': + query = query + .addSelect((subQuery) => { + return subQuery + .select('COUNT(request.id)', 'requestCount') + .from(MediaRequest, 'request') + .where('request.requestedBy.id = user.id'); + }, 'requestCount') + .orderBy('requestCount', 'DESC'); + break; + default: + query = query.orderBy('user.id', 'ASC'); + break; + } - return res - .status(200) - .json( - User.filterMany(users, req.user?.hasPermission(Permission.MANAGE_USERS)) - ); + const [users, userCount] = await query + .take(pageSize) + .skip(skip) + .getManyAndCount(); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(userCount / pageSize), + pageSize, + results: userCount, + page: Math.ceil(skip / pageSize) + 1, + }, + results: User.filterMany( + users, + req.user?.hasPermission(Permission.MANAGE_USERS) + ), + } as UserResultsResponse); + } catch (e) { + next({ status: 500, message: e.message }); + } }); router.post( diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 86505e6de..0be3bb008 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -17,6 +17,7 @@ const messages = defineMessages({ modifiedBy: 'Last Modified By', showingresults: 'Showing {from} to {to} of {total} results', + resultsperpage: 'Display {pageSize} results per page', next: 'Next', previous: 'Previous', filterAll: 'All', @@ -38,10 +39,11 @@ const RequestList: React.FC = () => { const [pageIndex, setPageIndex] = useState(0); const [currentFilter, setCurrentFilter] = useState('pending'); const [currentSort, setCurrentSort] = useState('added'); + const [currentPageSize, setCurrentPageSize] = useState(10); const { data, error, revalidate } = useSWR( - `/api/v1/request?take=10&skip=${ - pageIndex * 10 + `/api/v1/request?take=${currentPageSize}&skip=${ + pageIndex * currentPageSize }&filter=${currentFilter}&sort=${currentSort}` ); if (!data && !error) { @@ -160,9 +162,9 @@ const RequestList: React.FC = () => { })} {data.results.length === 0 && ( - + -
+
{intl.formatMessage(messages.noresults)} @@ -184,33 +186,56 @@ const RequestList: React.FC = () => { + + diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 2d8247af3..c805abf40 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -170,6 +170,7 @@ "components.RequestList.previous": "Previous", "components.RequestList.requestedAt": "Requested At", "components.RequestList.requests": "Requests", + "components.RequestList.resultsperpage": "Display {pageSize} results per page", "components.RequestList.showallrequests": "Show All Requests", "components.RequestList.showingresults": "Showing {from} to {to} of {total} results", "components.RequestList.sortAdded": "Request Date", @@ -635,14 +636,18 @@ "components.UserList.importfromplexerror": "Something went wrong while importing users from Plex.", "components.UserList.lastupdated": "Last Updated", "components.UserList.localuser": "Local User", + "components.UserList.next": "Next", "components.UserList.password": "Password", "components.UserList.passwordinfo": "Password Information", "components.UserList.passwordinfodescription": "Email notifications need to be configured and enabled in order to automatically generate passwords.", "components.UserList.permissions": "Permissions", "components.UserList.plexuser": "Plex User", + "components.UserList.previous": "Previous", + "components.UserList.resultsperpage": "Display {pageSize} results per page", "components.UserList.role": "Role", "components.UserList.save": "Save Changes", "components.UserList.saving": "Saving…", + "components.UserList.showingResults": "Showing {from} to {to} of {total} results", "components.UserList.sortCreated": "Creation Date", "components.UserList.sortDisplayName": "Display Name", "components.UserList.sortRequests": "Request Count", diff --git a/src/styles/globals.css b/src/styles/globals.css index 2f1080169..35229d752 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -102,8 +102,9 @@ select.rounded-r-only { @apply rounded-l-none; } -input.port { - @apply w-24; +input.short, +select.short { + @apply w-20; } .protocol {