Skip to content

Commit 4725903

Browse files
committed
torrent list pagination
1 parent 71a809b commit 4725903

6 files changed

Lines changed: 240 additions & 102 deletions

File tree

api/src/controllers/torrent.js

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,72 @@ export const getTorrentsPage = async ({
351351
},
352352
])
353353

354-
return await embellishTorrentsWithTrackerScrape(torrents)
354+
const [count] = await Torrent.aggregate([
355+
...(query
356+
? [
357+
{
358+
$match: {
359+
$or: [
360+
{ name: { $regex: query, $options: 'i' } },
361+
{ description: { $regex: query, $options: 'i' } },
362+
],
363+
},
364+
},
365+
]
366+
: []),
367+
...(category
368+
? [
369+
{
370+
$match: {
371+
type: category,
372+
},
373+
},
374+
]
375+
: []),
376+
...(userId
377+
? [
378+
{
379+
$match: {
380+
uploadedBy: userId,
381+
},
382+
},
383+
]
384+
: []),
385+
{
386+
$count: 'total',
387+
},
388+
])
389+
390+
return {
391+
torrents: await embellishTorrentsWithTrackerScrape(torrents),
392+
...count,
393+
}
394+
}
395+
396+
export const listLatest = async (req, res) => {
397+
let { count } = req.query
398+
count = parseInt(count) || 25
399+
count = Math.min(count, 100)
400+
try {
401+
const { torrents } = await getTorrentsPage({ limit: count })
402+
res.json(torrents)
403+
} catch (e) {
404+
res.status(500).send(e.message)
405+
}
406+
}
407+
408+
export const searchTorrents = async (req, res) => {
409+
const { query, category, page } = req.query
410+
try {
411+
const torrents = await getTorrentsPage({
412+
skip: page ? parseInt(page) : 0,
413+
query,
414+
category,
415+
})
416+
res.json(torrents)
417+
} catch (e) {
418+
res.status(500).send(e.message)
419+
}
355420
}
356421

357422
export const addComment = async (req, res) => {
@@ -383,33 +448,6 @@ export const addComment = async (req, res) => {
383448
}
384449
}
385450

386-
export const listLatest = async (req, res) => {
387-
let { count } = req.query
388-
count = parseInt(count) || 20
389-
count = Math.min(count, 100)
390-
try {
391-
const torrents = await getTorrentsPage({ limit: count })
392-
res.json(torrents)
393-
} catch (e) {
394-
res.status(500).send(e.message)
395-
}
396-
}
397-
398-
export const searchTorrents = async (req, res) => {
399-
const { query, category, page } = req.query
400-
try {
401-
const torrents = await getTorrentsPage({
402-
skip: page || 0,
403-
limit: 25,
404-
query,
405-
category,
406-
})
407-
res.json(torrents)
408-
} catch (e) {
409-
res.status(500).send(e.message)
410-
}
411-
}
412-
413451
export const addVote = async (req, res) => {
414452
const { infoHash, vote } = req.params
415453
try {

api/src/controllers/user.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,8 @@ export const fetchUser = async (req, res) => {
513513
}
514514

515515
user.ratio = await getUserRatio(user._id)
516-
user.torrents = await getTorrentsPage({ userId: user._id })
516+
const { torrents } = await getTorrentsPage({ userId: user._id })
517+
user.torrents = torrents
517518

518519
res.json(user)
519520
} catch (e) {

client/components/TorrentList.js

Lines changed: 135 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,153 @@
11
import React from 'react'
22
import getConfig from 'next/config'
3+
import { useRouter } from 'next/router'
34
import moment from 'moment'
45
import { ListUl } from '@styled-icons/boxicons-regular/ListUl'
56
import { Upload } from '@styled-icons/boxicons-regular/Upload'
67
import { Download } from '@styled-icons/boxicons-regular/Download'
78
import { Chat } from '@styled-icons/boxicons-solid/Chat'
9+
import { ChevronsLeft } from '@styled-icons/boxicons-solid/ChevronsLeft'
10+
import { ChevronLeft } from '@styled-icons/boxicons-solid/ChevronLeft'
11+
import { ChevronsRight } from '@styled-icons/boxicons-solid/ChevronsRight'
12+
import { ChevronRight } from '@styled-icons/boxicons-solid/ChevronRight'
813
import List from './List'
914
import Text from './Text'
15+
import Box from './Box'
16+
import Button from './Button'
1017

11-
const TorrentList = ({ torrents = [], categories }) => {
18+
const TorrentList = ({ torrents = [], categories, total }) => {
1219
const {
1320
publicRuntimeConfig: { SQ_SITE_WIDE_FREELEECH },
1421
} = getConfig()
1522

23+
const router = useRouter()
24+
const {
25+
asPath,
26+
query: { page: pageParam },
27+
} = router
28+
29+
const page = pageParam ? parseInt(pageParam) - 1 : 0
30+
31+
const maxPage = Math.floor(total / 25)
32+
const canPrevPage = page > 0
33+
const canNextPage = page < maxPage
34+
35+
const setPage = (number) => {
36+
if (number === 0) router.push(asPath.split('?')[0])
37+
else router.push(`${asPath.split('?')[0]}?page=${number + 1}`)
38+
}
39+
1640
return (
17-
<List
18-
data={torrents.map((torrent) => ({
19-
...torrent,
20-
href: `/torrent/${torrent.infoHash}`,
21-
}))}
22-
columns={[
23-
{
24-
header: 'Name',
25-
accessor: 'name',
26-
cell: ({ value, row }) => (
27-
<Text>
28-
{value}
29-
{(row.freeleech || SQ_SITE_WIDE_FREELEECH === true) && (
30-
<Text as="span" fontSize={0} color="primary" ml={3}>
31-
FL!
32-
</Text>
33-
)}
34-
</Text>
35-
),
36-
gridWidth: '2fr',
37-
},
38-
{
39-
header: 'Category',
40-
accessor: 'type',
41-
cell: ({ value }) => (
42-
<Text icon={ListUl}>
43-
{categories.find((c) => c.slug === value)?.name || 'None'}
44-
</Text>
45-
),
46-
gridWidth: '2fr',
47-
},
48-
{
49-
header: 'Seeders',
50-
accessor: 'seeders',
51-
cell: ({ value }) => <Text icon={Upload}>{value}</Text>,
52-
gridWidth: '1fr',
53-
},
54-
{
55-
header: 'Leechers',
56-
accessor: 'leechers',
57-
cell: ({ value }) => <Text icon={Download}>{value}</Text>,
58-
gridWidth: '1fr',
59-
},
60-
{
61-
header: 'Comments',
62-
accessor: 'comments.count',
63-
cell: ({ value }) => <Text icon={Chat}>{value || 0}</Text>,
64-
gridWidth: '1fr',
65-
},
66-
{
67-
header: 'Uploaded',
68-
accessor: 'created',
69-
cell: ({ value }) => (
70-
<Text>{moment(value).format('Do MMM YYYY')}</Text>
71-
),
72-
gridWidth: '1fr',
73-
rightAlign: true,
74-
},
75-
]}
76-
/>
41+
<>
42+
<List
43+
data={torrents.map((torrent) => ({
44+
...torrent,
45+
href: `/torrent/${torrent.infoHash}`,
46+
}))}
47+
columns={[
48+
{
49+
header: 'Name',
50+
accessor: 'name',
51+
cell: ({ value, row }) => (
52+
<Text>
53+
{value}
54+
{(row.freeleech || SQ_SITE_WIDE_FREELEECH === true) && (
55+
<Text as="span" fontSize={0} color="primary" ml={3}>
56+
FL!
57+
</Text>
58+
)}
59+
</Text>
60+
),
61+
gridWidth: '2fr',
62+
},
63+
{
64+
header: 'Category',
65+
accessor: 'type',
66+
cell: ({ value }) => (
67+
<Text icon={ListUl}>
68+
{categories.find((c) => c.slug === value)?.name || 'None'}
69+
</Text>
70+
),
71+
gridWidth: '2fr',
72+
},
73+
{
74+
header: 'Seeders',
75+
accessor: 'seeders',
76+
cell: ({ value }) => <Text icon={Upload}>{value}</Text>,
77+
gridWidth: '1fr',
78+
},
79+
{
80+
header: 'Leechers',
81+
accessor: 'leechers',
82+
cell: ({ value }) => <Text icon={Download}>{value}</Text>,
83+
gridWidth: '1fr',
84+
},
85+
{
86+
header: 'Comments',
87+
accessor: 'comments.count',
88+
cell: ({ value }) => <Text icon={Chat}>{value || 0}</Text>,
89+
gridWidth: '1fr',
90+
},
91+
{
92+
header: 'Uploaded',
93+
accessor: 'created',
94+
cell: ({ value }) => (
95+
<Text>{moment(value).format('Do MMM YYYY')}</Text>
96+
),
97+
gridWidth: '1fr',
98+
rightAlign: true,
99+
},
100+
]}
101+
/>
102+
{typeof total === 'number' && (
103+
<Box display="flex" alignItems="center" mt={4}>
104+
<Button
105+
onClick={() => setPage(0)}
106+
variant="secondary"
107+
disabled={!canPrevPage}
108+
px={1}
109+
py={1}
110+
mr={2}
111+
>
112+
<ChevronsLeft size={24} />
113+
</Button>
114+
<Button
115+
onClick={() => setPage(page - 1)}
116+
variant="secondary"
117+
disabled={!canPrevPage}
118+
px={1}
119+
py={1}
120+
mr={2}
121+
>
122+
<ChevronLeft size={24} />
123+
</Button>
124+
<Button
125+
onClick={() => setPage(page + 1)}
126+
variant="secondary"
127+
disabled={!canNextPage}
128+
px={1}
129+
py={1}
130+
mr={2}
131+
>
132+
<ChevronRight size={24} />
133+
</Button>
134+
<Button
135+
onClick={() => setPage(maxPage)}
136+
variant="secondary"
137+
disabled={!canNextPage}
138+
px={1}
139+
py={1}
140+
mr={3}
141+
>
142+
<ChevronsRight size={24} />
143+
</Button>
144+
<Text color="grey">
145+
{total.toLocaleString()} results — Page {page + 1} of{' '}
146+
{(maxPage + 1).toLocaleString()}
147+
</Text>
148+
</Box>
149+
)}
150+
</>
77151
)
78152
}
79153

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"next": "12.2.5",
2121
"polished": "^4.1.3",
2222
"pretty-bytes": "^5.6.0",
23+
"qs": "^6.11.0",
2324
"react": "17.0.2",
2425
"react-cookie": "^4.1.1",
2526
"react-dom": "17.0.2",

client/pages/categories/[category].js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import { useRouter } from 'next/router'
33
import getConfig from 'next/config'
4+
import qs from 'qs'
45
import { withAuthServerSideProps } from '../../utils/withAuth'
56
import SEO from '../../components/SEO'
67
import Text from '../../components/Text'
@@ -24,8 +25,12 @@ const Category = ({ results }) => {
2425
<Text as="h1" mb={5}>
2526
Browse {category.name}
2627
</Text>
27-
{results.length ? (
28-
<TorrentList torrents={results} categories={SQ_TORRENT_CATEGORIES} />
28+
{results?.torrents.length ? (
29+
<TorrentList
30+
torrents={results.torrents}
31+
categories={SQ_TORRENT_CATEGORIES}
32+
total={results.total}
33+
/>
2934
) : (
3035
<Text color="grey">No results.</Text>
3136
)}
@@ -34,18 +39,22 @@ const Category = ({ results }) => {
3439
}
3540

3641
export const getServerSideProps = withAuthServerSideProps(
37-
async ({ token, query: { category } }) => {
42+
async ({ token, query: { category, page: pageParam } }) => {
3843
if (!token) return { props: {} }
3944

4045
const {
4146
publicRuntimeConfig: { SQ_API_URL },
4247
} = getConfig()
4348

49+
const params = {
50+
category: encodeURIComponent(category),
51+
}
52+
const page = pageParam ? parseInt(pageParam) : 0
53+
if (page > 0) params.page = page
54+
4455
try {
4556
const searchRes = await fetch(
46-
`${SQ_API_URL}/torrents/search?category=${encodeURIComponent(
47-
category
48-
)}`,
57+
`${SQ_API_URL}/torrents/search?${qs.stringify(params)}`,
4958
{
5059
headers: {
5160
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)