Skip to content

Commit edc5e82

Browse files
committed
add sorting torrent list on categories, tags, search
1 parent 5eb4dd3 commit edc5e82

6 files changed

Lines changed: 153 additions & 41 deletions

File tree

api/src/controllers/torrent.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,19 @@ export const getTorrentsPage = async ({
452452
tag,
453453
uploadedBy,
454454
userId,
455+
sort,
455456
tracker,
456457
}) => {
457458
const queryNGrams = nGrams(query, false, 2, false).join(" ");
458459

460+
const [sortField, sortDirString] = sort?.split(":") ?? [];
461+
const sortDir = sortDirString === "asc" ? 1 : -1;
462+
463+
const combinedSort = {};
464+
if (sortField) combinedSort[sortField] = sortDir;
465+
if (query) combinedSort.confidenceScore = { $meta: "textScore" };
466+
combinedSort.created = -1;
467+
459468
const torrents = await Torrent.aggregate([
460469
...(query
461470
? [
@@ -529,17 +538,6 @@ export const getTorrentsPage = async ({
529538
},
530539
]
531540
: []),
532-
{
533-
$sort: query
534-
? { confidenceScore: { $meta: "textScore" } }
535-
: { created: -1 },
536-
},
537-
{
538-
$skip: skip,
539-
},
540-
{
541-
$limit: limit,
542-
},
543541
{
544542
$lookup: {
545543
from: "comments",
@@ -588,6 +586,15 @@ export const getTorrentsPage = async ({
588586
},
589587
},
590588
{ $unwind: { path: "$fetchedBy", preserveNullAndEmptyArrays: true } },
589+
{
590+
$sort: combinedSort,
591+
},
592+
{
593+
$skip: skip,
594+
},
595+
{
596+
$limit: limit,
597+
},
591598
]);
592599

593600
const [count] = await Torrent.aggregate([
@@ -682,7 +689,7 @@ export const listAll = async (req, res, next) => {
682689
};
683690

684691
export const searchTorrents = (tracker) => async (req, res, next) => {
685-
const { query, category, source, tag, page } = req.query;
692+
const { query, category, source, tag, page, sort } = req.query;
686693
try {
687694
const torrents = await getTorrentsPage({
688695
skip: page ? parseInt(page) : 0,
@@ -691,6 +698,7 @@ export const searchTorrents = (tracker) => async (req, res, next) => {
691698
source,
692699
tag: tag ? decodeURIComponent(tag) : undefined,
693700
userId: req.userId,
701+
sort: sort ? decodeURIComponent(sort) : undefined,
694702
tracker,
695703
});
696704
res.json(torrents);

client/components/List.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import Link from "next/link";
3+
import { useRouter } from "next/router";
34
import { toPath } from "lodash";
5+
import qs from "qs";
6+
import { CaretUp } from "@styled-icons/boxicons-regular/CaretUp";
7+
import { CaretDown } from "@styled-icons/boxicons-regular/CaretDown";
48
import Box from "../components/Box";
59
import Text from "../components/Text";
610

@@ -47,7 +51,17 @@ const ListItem = ({ children }) => {
4751
);
4852
};
4953

54+
const getSortIcon = (accessor, sort = "") => {
55+
const [sortAccessor, sortDirection] = sort.split(":");
56+
if (accessor !== sortAccessor) return null;
57+
if (sortDirection === "asc") return CaretUp;
58+
if (sortDirection === "desc") return CaretDown;
59+
return null;
60+
};
61+
5062
const List = ({ data = [], columns = [], ...rest }) => {
63+
const router = useRouter();
64+
const { sort } = router.query;
5165
return (
5266
<Box overflowX="auto">
5367
<Box minWidth="700px">
@@ -65,7 +79,42 @@ const List = ({ data = [], columns = [], ...rest }) => {
6579
fontWeight={600}
6680
fontSize={1}
6781
textAlign={col.rightAlign ? "right" : "left"}
68-
_css={{ textTransform: "uppercase" }}
82+
_css={{
83+
textTransform: "uppercase",
84+
cursor: col.sortable ? "pointer" : "text",
85+
userSelect: col.sortable ? "none" : "auto",
86+
}}
87+
onClick={
88+
col.sortable
89+
? () => {
90+
const query = window.location.search;
91+
const parsed = qs.parse(query.replace("?", ""));
92+
if (parsed.sort) {
93+
const [accessor, direction] = parsed.sort.split(":");
94+
if (accessor === col.accessor) {
95+
if (direction === "asc")
96+
parsed.sort = `${col.accessor}:desc`;
97+
else if (direction === "desc") delete parsed.sort;
98+
} else {
99+
parsed.sort = `${col.accessor}:asc`;
100+
}
101+
} else {
102+
parsed.sort = `${col.accessor}:asc`;
103+
}
104+
router.replace(
105+
Object.keys(parsed).length
106+
? `${window.location.pathname}?${qs.stringify(
107+
parsed
108+
)}`
109+
: window.location.pathname
110+
);
111+
}
112+
: undefined
113+
}
114+
icon={getSortIcon(col.accessor, sort)}
115+
iconTextWrapperProps={{
116+
justifyContent: col.rightAlign ? "flex-end" : "flex-start",
117+
}}
69118
>
70119
{col.header}
71120
</Text>

client/components/TorrentList.js

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React from "react";
1+
import React, { useEffect } from "react";
22
import getConfig from "next/config";
33
import { useRouter } from "next/router";
44
import moment from "moment";
55
import slugify from "slugify";
6+
import qs from "qs";
67
import { ListUl } from "@styled-icons/boxicons-regular/ListUl";
78
import { Upload } from "@styled-icons/boxicons-regular/Upload";
89
import { Download } from "@styled-icons/boxicons-regular/Download";
@@ -18,28 +19,62 @@ import Text from "./Text";
1819
import Box from "./Box";
1920
import Button from "./Button";
2021

21-
const TorrentList = ({ torrents = [], categories, total }) => {
22+
const pageSize = 25;
23+
24+
const TorrentList = ({
25+
torrents = [],
26+
setTorrents,
27+
categories,
28+
total,
29+
fetchPath,
30+
token,
31+
}) => {
2232
const {
2333
publicRuntimeConfig: { SQ_SITE_WIDE_FREELEECH },
2434
} = getConfig();
2535

2636
const router = useRouter();
2737
const {
28-
asPath,
29-
query: { page: pageParam },
38+
query: { page: pageParam, sort },
3039
} = router;
3140

3241
const page = pageParam ? parseInt(pageParam) - 1 : 0;
3342

34-
const maxPage = Math.floor(total / 25);
43+
const maxPage = total > pageSize ? Math.floor(total / pageSize) : 0;
3544
const canPrevPage = page > 0;
3645
const canNextPage = page < maxPage;
3746

3847
const setPage = (number) => {
39-
if (number === 0) router.push(asPath.split("?")[0]);
40-
else router.push(`${asPath.split("?")[0]}?page=${number + 1}`);
48+
const query = qs.parse(window.location.search.replace("?", ""));
49+
if (number === 0) delete query.page;
50+
else query.page = number + 1;
51+
router.push(
52+
Object.keys(query).length
53+
? `${window.location.pathname}?${qs.stringify(query)}`
54+
: window.location.pathname
55+
);
4156
};
4257

58+
useEffect(() => {
59+
const fetchTorrents = async () => {
60+
try {
61+
const searchRes = await fetch(
62+
`${fetchPath}?${qs.stringify(router.query)}`,
63+
{
64+
headers: {
65+
Authorization: `Bearer ${token}`,
66+
"Content-Type": "application/json",
67+
},
68+
}
69+
);
70+
71+
const results = await searchRes.json();
72+
setTorrents(results.torrents);
73+
} catch (e) {}
74+
};
75+
if (fetchPath && token) fetchTorrents();
76+
}, [sort, page]);
77+
4378
return (
4479
<>
4580
<List
@@ -95,6 +130,7 @@ const TorrentList = ({ torrents = [], categories, total }) => {
95130
),
96131
gridWidth: "100px",
97132
rightAlign: true,
133+
sortable: !!token,
98134
},
99135
{
100136
header: "Leechers",
@@ -109,6 +145,7 @@ const TorrentList = ({ torrents = [], categories, total }) => {
109145
),
110146
gridWidth: "100px",
111147
rightAlign: true,
148+
sortable: !!token,
112149
},
113150
{
114151
header: "Downloads",
@@ -121,8 +158,9 @@ const TorrentList = ({ torrents = [], categories, total }) => {
121158
{value || 0}
122159
</Text>
123160
),
124-
gridWidth: "100px",
161+
gridWidth: "115px",
125162
rightAlign: true,
163+
sortable: !!token,
126164
},
127165
{
128166
header: "Comments",
@@ -135,8 +173,9 @@ const TorrentList = ({ torrents = [], categories, total }) => {
135173
{value || 0}
136174
</Text>
137175
),
138-
gridWidth: "100px",
176+
gridWidth: "110px",
139177
rightAlign: true,
178+
sortable: !!token,
140179
},
141180
{
142181
header: "Uploaded",
@@ -146,6 +185,7 @@ const TorrentList = ({ torrents = [], categories, total }) => {
146185
),
147186
gridWidth: "140px",
148187
rightAlign: true,
188+
sortable: !!token,
149189
},
150190
]}
151191
/>

client/pages/categories/[category].js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import { useRouter } from "next/router";
33
import getConfig from "next/config";
44
import qs from "qs";
@@ -8,14 +8,16 @@ import SEO from "../../components/SEO";
88
import Text from "../../components/Text";
99
import TorrentList from "../../components/TorrentList";
1010

11-
const Category = ({ results }) => {
11+
const Category = ({ results, token }) => {
12+
const [torrents, setTorrents] = useState(results?.torrents ?? []);
13+
1214
const router = useRouter();
1315
const {
1416
query: { category: categorySlug },
1517
} = router;
1618

1719
const {
18-
publicRuntimeConfig: { SQ_TORRENT_CATEGORIES },
20+
publicRuntimeConfig: { SQ_TORRENT_CATEGORIES, SQ_API_URL },
1921
} = getConfig();
2022

2123
const category = Object.keys(SQ_TORRENT_CATEGORIES).find(
@@ -28,11 +30,14 @@ const Category = ({ results }) => {
2830
<Text as="h1" mb={5}>
2931
Browse {category}
3032
</Text>
31-
{results?.torrents.length ? (
33+
{torrents.length ? (
3234
<TorrentList
33-
torrents={results.torrents}
35+
torrents={torrents}
36+
setTorrents={setTorrents}
3437
categories={SQ_TORRENT_CATEGORIES}
3538
total={results.total}
39+
fetchPath={`${SQ_API_URL}/torrent/search`}
40+
token={token}
3641
/>
3742
) : (
3843
<Text color="grey">No results.</Text>
@@ -74,7 +79,7 @@ export const getServerSideProps = withAuthServerSideProps(
7479
throw "banned";
7580
}
7681
const results = await searchRes.json();
77-
return { props: { results } };
82+
return { props: { results, token } };
7883
} catch (e) {
7984
if (e === "banned") throw "banned";
8085
return { props: {} };

client/pages/search/[[...query]].js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import getConfig from "next/config";
33
import { useRouter } from "next/router";
44
import qs from "qs";
@@ -10,15 +10,17 @@ import Button from "../../components/Button";
1010
import Box from "../../components/Box";
1111
import TorrentList from "../../components/TorrentList";
1212

13-
const Search = ({ results, error }) => {
13+
const Search = ({ results, error, token }) => {
14+
const [torrents, setTorrents] = useState(results?.torrents ?? []);
15+
1416
const router = useRouter();
1517
let {
1618
query: { query },
1719
} = router;
1820
query = query ? decodeURIComponent(query) : "";
1921

2022
const {
21-
publicRuntimeConfig: { SQ_TORRENT_CATEGORIES },
23+
publicRuntimeConfig: { SQ_TORRENT_CATEGORIES, SQ_API_URL },
2224
} = getConfig();
2325

2426
const handleSearch = (e) => {
@@ -44,11 +46,14 @@ const Search = ({ results, error }) => {
4446
<>
4547
{query && (
4648
<>
47-
{results.torrents.length ? (
49+
{torrents.length ? (
4850
<TorrentList
49-
torrents={results.torrents}
51+
torrents={torrents}
52+
setTorrents={setTorrents}
5053
categories={SQ_TORRENT_CATEGORIES}
5154
total={results.total}
55+
fetchPath={`${SQ_API_URL}/torrent/search`}
56+
token={token}
5257
/>
5358
) : (
5459
<Text color="grey">No results.</Text>
@@ -92,7 +97,7 @@ export const getServerSideProps = withAuthServerSideProps(
9297
return { props: { error: message } };
9398
} else {
9499
const results = await searchRes.json();
95-
return { props: { results } };
100+
return { props: { results, token } };
96101
}
97102
} catch (e) {
98103
if (e === "banned") throw "banned";

0 commit comments

Comments
 (0)