Skip to content

Commit

Permalink
added sorting to history endpoints and changed Activity tables to app…
Browse files Browse the repository at this point in the history
…ly sorting to api calls
  • Loading branch information
CyferShepard committed Jan 4, 2025
1 parent cd0f2ae commit 76363ea
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 45 deletions.
3 changes: 2 additions & 1 deletion backend/classes/db-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ function wrapField(field) {
field.includes("MIN") ||
field.includes("AVG") ||
field.includes("DISTINCT") ||
field.includes("json_agg")
field.includes("json_agg") ||
field.includes("CASE")
) {
return field;
}
Expand Down
163 changes: 132 additions & 31 deletions backend/routes/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ const { tables } = require("../global/backup_tables");

const router = express.Router();

//consts
const groupedSortMap = [
{ field: "UserName", column: "a.UserName" },
{ field: "RemoteEndPoint", column: "a.RemoteEndPoint" },
{ field: "NowPlayingItemName", column: "FullName" },
{ field: "Client", column: "a.Client" },
{ field: "DeviceName", column: "a.DeviceName" },
{ field: "ActivityDateInserted", column: "a.ActivityDateInserted" },
{ field: "PlaybackDuration", column: "ar.TotalDuration" },
{ field: "TotalPlays", column: "TotalPlays" },
];

const unGroupedSortMap = [
{ field: "UserName", column: "a.UserName" },
{ field: "RemoteEndPoint", column: "a.RemoteEndPoint" },
{ field: "NowPlayingItemName", column: "FullName" },
{ field: "Client", column: "a.Client" },
{ field: "DeviceName", column: "a.DeviceName" },
{ field: "ActivityDateInserted", column: "a.ActivityDateInserted" },
{ field: "PlaybackDuration", column: "a.PlaybackDuration" },
];

//Functions
function groupRecentlyAdded(rows) {
const groupedResults = {};
Expand Down Expand Up @@ -1058,7 +1080,9 @@ router.post("/setExcludedBackupTable", async (req, res) => {

//DB Queries - History
router.get("/getHistory", async (req, res) => {
const { size = 50, page = 1, search } = req.query;
const { size = 50, page = 1, search, sort = "ActivityDateInserted", desc = true } = req.query;

const sortField = groupedSortMap.find((item) => item.field === sort)?.column || "a.ActivityDateInserted";

try {
const cte = {
Expand All @@ -1078,7 +1102,21 @@ router.get("/getHistory", async (req, res) => {

const query = {
cte: cte,
select: ["a.*", "a.EpisodeNumber", "a.SeasonNumber", "a.ParentId", "ar.results", "ar.TotalPlays", "ar.TotalDuration"],
select: [
"a.*",
"a.EpisodeNumber",
"a.SeasonNumber",
"a.ParentId",
"ar.results",
"ar.TotalPlays",
"ar.TotalDuration",
`
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END AS "FullName"
`,
],
table: "js_latest_playback_activity",
alias: "a",
joins: [
Expand All @@ -1094,16 +1132,21 @@ router.get("/getHistory", async (req, res) => {
},
],

order_by: "a.ActivityDateInserted",
sort_order: "desc",
order_by: sortField,
sort_order: desc ? "desc" : "asc",
pageNumber: page,
pageSize: size,
};

if (search && search.length > 0) {
query.where = [
{
field: `LOWER(COALESCE(a."SeriesName" || ' - ' || a."NowPlayingItemName", a."NowPlayingItemName"))`,
field: `LOWER(
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END
)`,
operator: "LIKE",
value: `${search.toLowerCase()}`,
},
Expand All @@ -1115,10 +1158,11 @@ router.get("/getHistory", async (req, res) => {
...item,
PlaybackDuration: item.TotalDuration ? item.TotalDuration : item.PlaybackDuration,
}));
const response = { current_page: page, pages: result.pages, size: size, results: result.results };
const response = { current_page: page, pages: result.pages, size: size, sort: sort, desc: desc, results: result.results };
if (search && search.length > 0) {
response.search = search;
}

res.send(response);
} catch (error) {
console.log(error);
Expand All @@ -1127,7 +1171,7 @@ router.get("/getHistory", async (req, res) => {

router.post("/getLibraryHistory", async (req, res) => {
try {
const { size = 50, page = 1, search } = req.query;
const { size = 50, page = 1, search, sort = "ActivityDateInserted", desc = true } = req.query;
const { libraryid } = req.body;

if (libraryid === undefined) {
Expand All @@ -1136,6 +1180,8 @@ router.post("/getLibraryHistory", async (req, res) => {
return;
}

const sortField = groupedSortMap.find((item) => item.field === sort)?.column || "a.ActivityDateInserted";

const cte = {
cteAlias: "activity_results",
select: [
Expand All @@ -1153,7 +1199,21 @@ router.post("/getLibraryHistory", async (req, res) => {

const query = {
cte: cte,
select: ["a.*", "a.EpisodeNumber", "a.SeasonNumber", "a.ParentId", "ar.results", "ar.TotalPlays", "ar.TotalDuration"],
select: [
"a.*",
"a.EpisodeNumber",
"a.SeasonNumber",
"a.ParentId",
"ar.results",
"ar.TotalPlays",
"ar.TotalDuration",
`
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END AS "FullName"
`,
],
table: "js_latest_playback_activity",
alias: "a",
joins: [
Expand All @@ -1178,18 +1238,23 @@ router.post("/getLibraryHistory", async (req, res) => {
},
],

order_by: "a.ActivityDateInserted",
sort_order: "desc",
order_by: sortField,
sort_order: desc ? "desc" : "asc",
pageNumber: page,
pageSize: size,
};

if (search && search.length > 0) {
query.where = [
{
field: `LOWER(COALESCE(a."SeriesName" || ' - ' || a."NowPlayingItemName", a."NowPlayingItemName"))`,
field: `LOWER(
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END
)`,
operator: "LIKE",
value: `%${search.toLowerCase()}%`,
value: `${search.toLowerCase()}`,
},
];
}
Expand All @@ -1201,7 +1266,7 @@ router.post("/getLibraryHistory", async (req, res) => {
PlaybackDuration: item.TotalDuration ? item.TotalDuration : item.PlaybackDuration,
}));

const response = { current_page: page, pages: result.pages, size: size, results: result.results };
const response = { current_page: page, pages: result.pages, size: size, sort: sort, desc: desc, results: result.results };
if (search && search.length > 0) {
response.search = search;
}
Expand All @@ -1215,7 +1280,7 @@ router.post("/getLibraryHistory", async (req, res) => {

router.post("/getItemHistory", async (req, res) => {
try {
const { size = 50, page = 1, search } = req.query;
const { size = 50, page = 1, search, sort = "ActivityDateInserted", desc = true } = req.query;
const { itemid } = req.body;

if (itemid === undefined) {
Expand All @@ -1224,8 +1289,21 @@ router.post("/getItemHistory", async (req, res) => {
return;
}

const sortField = unGroupedSortMap.find((item) => item.field === sort)?.column || "a.ActivityDateInserted";

const query = {
select: ["a.*", "a.EpisodeNumber", "a.SeasonNumber", "a.ParentId"],
select: [
"a.*",
"a.EpisodeNumber",
"a.SeasonNumber",
"a.ParentId",
`
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END AS "FullName"
`,
],
table: "jf_playback_activity_with_metadata",
alias: "a",
where: [
Expand All @@ -1235,25 +1313,30 @@ router.post("/getItemHistory", async (req, res) => {
{ column: "a.NowPlayingItemId", operator: "=", value: itemid, type: "or" },
],
],
order_by: "ActivityDateInserted",
sort_order: "desc",
order_by: sortField,
sort_order: desc ? "desc" : "asc",
pageNumber: page,
pageSize: size,
};

if (search && search.length > 0) {
query.where.push([
query.where = [
{
field: `LOWER(COALESCE(a."SeriesName" || ' - ' || a."NowPlayingItemName", a."NowPlayingItemName"))`,
field: `LOWER(
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END
)`,
operator: "LIKE",
value: `%${search.toLowerCase()}%`,
value: `${search.toLowerCase()}`,
},
]);
];
}

const result = await dbHelper.query(query);

const response = { current_page: page, pages: result.pages, size: size, results: result.results };
const response = { current_page: page, pages: result.pages, size: size, sort: sort, desc: desc, results: result.results };
if (search && search.length > 0) {
response.search = search;
}
Expand All @@ -1267,7 +1350,7 @@ router.post("/getItemHistory", async (req, res) => {

router.post("/getUserHistory", async (req, res) => {
try {
const { size = 50, page = 1, search } = req.query;
const { size = 50, page = 1, search, sort = "ActivityDateInserted", desc = true } = req.query;
const { userid } = req.body;

if (userid === undefined) {
Expand All @@ -1276,29 +1359,47 @@ router.post("/getUserHistory", async (req, res) => {
return;
}

const sortField = unGroupedSortMap.find((item) => item.field === sort)?.column || "a.ActivityDateInserted";

const query = {
select: ["a.*", "a.EpisodeNumber", "a.SeasonNumber", "a.ParentId"],
select: [
"a.*",
"a.EpisodeNumber",
"a.SeasonNumber",
"a.ParentId",
`
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END AS "FullName"
`,
],
table: "jf_playback_activity_with_metadata",
alias: "a",
where: [[{ column: "a.UserId", operator: "=", value: userid }]],
order_by: "ActivityDateInserted",
sort_order: "desc",
order_by: sortField,
sort_order: desc ? "desc" : "asc",
pageNumber: page,
pageSize: size,
};

if (search && search.length > 0) {
query.where.push([
query.where = [
{
field: `LOWER(COALESCE(a."SeriesName" || ' - ' || a."NowPlayingItemName", a."NowPlayingItemName"))`,
field: `LOWER(
CASE
WHEN a."SeriesName" is null THEN a."NowPlayingItemName"
ELSE CONCAT(a."SeriesName" , ' : S' , a."SeasonNumber" , 'E' , a."EpisodeNumber" , ' - ' , a."NowPlayingItemName")
END
)`,
operator: "LIKE",
value: `%${search.toLowerCase()}%`,
value: `${search.toLowerCase()}`,
},
]);
];
}
const result = await dbHelper.query(query);

const response = { current_page: page, pages: result.pages, size: size, results: result.results };
const response = { current_page: page, pages: result.pages, size: size, sort: sort, desc: desc, results: result.results };

if (search && search.length > 0) {
response.search = search;
Expand Down
14 changes: 11 additions & 3 deletions src/pages/activity.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ function Activity() {
const [libraries, setLibraries] = useState([]);
const [showLibraryFilters, setShowLibraryFilters] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [sorting, setSorting] = useState({ column: "ActivityDateInserted", desc: true });
const [isBusy, setIsBusy] = useState(false);

const handlePageChange = (newPage) => {
setCurrentPage(newPage);
};

const onSortChange = (sort) => {
setSorting({ column: sort.column, desc: sort.desc });
};

function setItemLimit(limit) {
setItemCount(parseInt(limit));
localStorage.setItem("PREF_ACTIVITY_ItemCount", limit);
Expand Down Expand Up @@ -82,7 +87,7 @@ function Activity() {

const fetchHistory = () => {
setIsBusy(true);
const url = `/api/getHistory?size=${itemCount}&page=${currentPage}&search=${debouncedSearchQuery}`;
const url = `/api/getHistory?size=${itemCount}&page=${currentPage}&search=${debouncedSearchQuery}&sort=${sorting.column}&desc=${sorting.desc}`;
axios
.get(url, {
headers: {
Expand Down Expand Up @@ -136,7 +141,9 @@ function Activity() {
!data ||
(data.current_page && data.current_page !== currentPage) ||
(data.size && data.size !== itemCount) ||
(data.search ? data.search : "") !== debouncedSearchQuery.trim()
(data?.search ?? "") !== debouncedSearchQuery.trim() ||
(data?.sort ?? "") !== sorting.column ||
(data?.desc ?? true) !== sorting.desc
) {
fetchHistory();
fetchLibraries();
Expand All @@ -149,7 +156,7 @@ function Activity() {

const intervalId = setInterval(fetchHistory, 60000 * 60);
return () => clearInterval(intervalId);
}, [data, config, itemCount, currentPage, debouncedSearchQuery]);
}, [data, config, itemCount, currentPage, debouncedSearchQuery, sorting]);

if (!data) {
return <Loading />;
Expand Down Expand Up @@ -271,6 +278,7 @@ function Activity() {
data={filteredData}
itemCount={itemCount}
onPageChange={handlePageChange}
onSortChange={onSortChange}
pageCount={data.pages}
isBusy={isBusy}
/>
Expand Down
Loading

0 comments on commit 76363ea

Please sign in to comment.