mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
fixed migration sequence
Had duplicate migration sequences Minor css changes and link rewrites to take you the specific item in the library overview for last watched items
This commit is contained in:
87
backend/migrations/025_fs_last_library_activity_function.js
Normal file
87
backend/migrations/025_fs_last_library_activity_function.js
Normal file
@@ -0,0 +1,87 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.raw(`
|
||||
DROP FUNCTION IF EXISTS fs_last_library_activity(text);
|
||||
CREATE OR REPLACE FUNCTION public.fs_last_library_activity(
|
||||
libraryid text)
|
||||
RETURNS TABLE("Id" text,"EpisodeId" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval)
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
ROWS 1000
|
||||
|
||||
AS $BODY$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT DISTINCT ON (i."Name", e."Name")
|
||||
i."Id",
|
||||
a."EpisodeId",
|
||||
i."Name",
|
||||
e."Name" AS "EpisodeName",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber",
|
||||
i."PrimaryImageHash",
|
||||
a."UserId",
|
||||
a."UserName",
|
||||
(NOW() - a."ActivityDateInserted") as "LastPlayed"
|
||||
FROM jf_playback_activity a
|
||||
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
JOIN jf_libraries l ON i."ParentId" = l."Id"
|
||||
LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId"
|
||||
LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId"
|
||||
WHERE l."Id" = libraryid
|
||||
ORDER BY i."Name", e."Name", a."ActivityDateInserted" DESC
|
||||
) AS latest_distinct_rows
|
||||
ORDER BY "LastPlayed"
|
||||
LIMIT 15;
|
||||
END;
|
||||
|
||||
$BODY$;
|
||||
|
||||
`).catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.raw(`
|
||||
DROP FUNCTION IF EXISTS fs_last_library_activity(text);
|
||||
CREATE OR REPLACE FUNCTION fs_last_library_activity(
|
||||
libraryid text)
|
||||
RETURNS TABLE("Id" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval)
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
ROWS 1000
|
||||
|
||||
AS $BODY$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT DISTINCT ON (i."Name", e."Name")
|
||||
i."Id",
|
||||
i."Name",
|
||||
e."Name" AS "EpisodeName",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber",
|
||||
i."PrimaryImageHash",
|
||||
a."UserId",
|
||||
a."UserName",
|
||||
(NOW() - a."ActivityDateInserted") as "LastPlayed"
|
||||
FROM jf_playback_activity a
|
||||
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
JOIN jf_libraries l ON i."ParentId" = l."Id"
|
||||
LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId"
|
||||
LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId"
|
||||
WHERE l."Id" = libraryid
|
||||
ORDER BY i."Name", e."Name", a."ActivityDateInserted" DESC
|
||||
) AS latest_distinct_rows
|
||||
ORDER BY "LastPlayed"
|
||||
LIMIT 15;
|
||||
END;
|
||||
$BODY$;
|
||||
`);
|
||||
};
|
||||
|
||||
96
backend/migrations/026_fs_last_user_activity_function.js
Normal file
96
backend/migrations/026_fs_last_user_activity_function.js
Normal file
@@ -0,0 +1,96 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.raw(`
|
||||
DROP FUNCTION IF EXISTS fs_last_user_activity(text);
|
||||
CREATE OR REPLACE FUNCTION public.fs_last_user_activity(
|
||||
userid text)
|
||||
RETURNS TABLE("Id" text,"EpisodeId" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval)
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
ROWS 1000
|
||||
|
||||
AS $BODY$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT DISTINCT ON (i."Name", e."Name")
|
||||
i."Id",
|
||||
a."EpisodeId",
|
||||
i."Name",
|
||||
e."Name" AS "EpisodeName",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber",
|
||||
i."PrimaryImageHash",
|
||||
a."UserId",
|
||||
a."UserName",
|
||||
(NOW() - a."ActivityDateInserted") as "LastPlayed"
|
||||
FROM jf_playback_activity a
|
||||
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId"
|
||||
LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId"
|
||||
WHERE a."UserId" = userid
|
||||
) AS latest_distinct_rows
|
||||
ORDER BY "LastPlayed";
|
||||
END;
|
||||
|
||||
$BODY$;
|
||||
|
||||
ALTER FUNCTION fs_last_user_activity(text)
|
||||
OWNER TO ${process.env.POSTGRES_USER};
|
||||
`).catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.raw(`
|
||||
DROP FUNCTION IF EXISTS fs_last_user_activity(text);
|
||||
CREATE OR REPLACE FUNCTION fs_last_user_activity(
|
||||
userid text
|
||||
)
|
||||
RETURNS TABLE(
|
||||
"Id" text,
|
||||
"Name" text,
|
||||
"EpisodeName" text,
|
||||
"SeasonNumber" integer,
|
||||
"EpisodeNumber" integer,
|
||||
"PrimaryImageHash" text,
|
||||
"UserId" text,
|
||||
"UserName" text,
|
||||
"LastPlayed" interval
|
||||
)
|
||||
LANGUAGE 'plpgsql'
|
||||
COST 100
|
||||
VOLATILE PARALLEL UNSAFE
|
||||
ROWS 1000
|
||||
AS $BODY$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT DISTINCT ON (i."Name", e."Name")
|
||||
i."Id",
|
||||
i."Name",
|
||||
e."Name" AS "EpisodeName",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber",
|
||||
CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber",
|
||||
i."PrimaryImageHash",
|
||||
a."UserId",
|
||||
a."UserName",
|
||||
(NOW() - a."ActivityDateInserted") as "LastPlayed"
|
||||
FROM jf_playback_activity a
|
||||
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId"
|
||||
LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId"
|
||||
WHERE a."UserId" = userid
|
||||
) AS latest_distinct_rows
|
||||
ORDER BY "LastPlayed";
|
||||
END;
|
||||
$BODY$;
|
||||
|
||||
ALTER FUNCTION fs_last_user_activity(text)
|
||||
OWNER TO ${process.env.POSTGRES_USER};
|
||||
`);
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ function LastWatchedCard(props) {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
return (
|
||||
<div className="last-card">
|
||||
<Link to={`/item/${props.data.Id}`}>
|
||||
<Link to={`/item/${props.data.EpisodeId||props.data.Id}`}>
|
||||
<div className="last-card-banner">
|
||||
{loaded ? null : <Blurhash hash={props.data.PrimaryImageHash} width={'100%'} height={'100%'}/>}
|
||||
<img
|
||||
|
||||
@@ -33,7 +33,6 @@ useEffect(() => {
|
||||
if(config){
|
||||
setRefresh(true);
|
||||
try {
|
||||
console.log(Id);
|
||||
const itemData = await axios.post(`/api/getItemDetails`, {
|
||||
Id: Id
|
||||
}, {
|
||||
@@ -64,7 +63,7 @@ useEffect(() => {
|
||||
return () => clearInterval(intervalId);
|
||||
}, [config, Id]);
|
||||
|
||||
console.log(data);
|
||||
|
||||
|
||||
|
||||
if(!data)
|
||||
|
||||
@@ -75,7 +75,6 @@ function ItemDetails(props) {
|
||||
</h1>
|
||||
|
||||
<div className="my-3">
|
||||
{props.data.CommunityRating ? <p style={{color:"lightgrey", fontSize:"0.8em", fontStyle:"italic"}}>Community Rating: {props.data.CommunityRating}</p> :<></>}
|
||||
{props.data.Type==="Episode"? <p><Link to={`/item/${props.data.SeasonId}`} className="fw-bold">{props.data.SeasonName}</Link> Episode {props.data.IndexNumber} - {props.data.Name}</p> : <></> }
|
||||
{props.data.Type==="Season"? <p>{props.data.Name}</p> : <></> }
|
||||
{props.data.FileName ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">File Name: {props.data.FileName}</p> :<></>}
|
||||
|
||||
@@ -14,7 +14,7 @@ function LibraryStatComponent(props) {
|
||||
};
|
||||
|
||||
const cardBgStyle = {
|
||||
backdropFilter: 'blur(10px)',
|
||||
backdropFilter: 'blur(5px)',
|
||||
backgroundColor: 'rgb(0, 0, 0, 0.6)',
|
||||
height:'100%',
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ function sessionCard(props) {
|
||||
};
|
||||
|
||||
const cardBgStyle = {
|
||||
backdropFilter: 'blur(10px)',
|
||||
backdropFilter: 'blur(5px)',
|
||||
backgroundColor: 'rgb(0, 0, 0, 0.6)',
|
||||
height:'100%',
|
||||
};
|
||||
|
||||
@@ -17,11 +17,11 @@ function ItemStatComponent(props) {
|
||||
const cardStyle = {
|
||||
backgroundImage: `url(${props.base_url}/Items/${props.data[0].Id}/Images/Backdrop/?fillWidth=300&quality=10), linear-gradient(to right, #00A4DC, #AA5CC3)`,
|
||||
height:'100%',
|
||||
backgroundSize: 'contain',
|
||||
backgroundSize: 'cover',
|
||||
};
|
||||
|
||||
const cardBgStyle = {
|
||||
backdropFilter: 'blur(10px)',
|
||||
backdropFilter: 'blur(5px)',
|
||||
backgroundColor: 'rgb(0, 0, 0, 0.6)',
|
||||
height:'100%',
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
grid-auto-rows: 120px;
|
||||
background-color: rgb(100, 100, 100,0.2);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
background-color: rgb(100, 100, 100,0.2);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.item-name
|
||||
@@ -16,7 +16,7 @@
|
||||
.item-image
|
||||
{
|
||||
max-width: 200px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
overflow-x: auto;
|
||||
background-color: rgb(100, 100, 100,0.2);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
margin-bottom: 20px;
|
||||
min-height: 300px;
|
||||
@@ -41,7 +41,7 @@
|
||||
margin-right: 20px;
|
||||
|
||||
width: 150px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 520px));
|
||||
grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/
|
||||
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
/* margin-right: 20px; */
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
.library{
|
||||
width: 100%;
|
||||
padding: 5px 20px;
|
||||
backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
background-color: rgb(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,14 @@
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
background-color: #1e1c22;
|
||||
transition: opacity 800ms ease-in;
|
||||
opacity: 1;
|
||||
}
|
||||
.loading::before
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.component-loading {
|
||||
|
||||
height: inherit;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
grid-auto-rows: 340px;/* max-width+offset so 215 + 20*/
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
margin-right: 20px;
|
||||
color: white;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
height: 320px;
|
||||
width: 185px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
/* Add a third row that takes up remaining space */
|
||||
@@ -36,7 +36,7 @@
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center top;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
position: relative;
|
||||
/* margin: 4px; */
|
||||
/* margin: 8px; */
|
||||
|
||||
/* background-color: #f71b1b; */
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
/* margin-bottom: 10px; */
|
||||
|
||||
background-size: cover;
|
||||
border-radius: 4px 4px 0px 4px;
|
||||
border-radius: 8px 8px 0px 8px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
@@ -36,7 +36,7 @@
|
||||
grid-column: 1/3; */
|
||||
height: 5px;
|
||||
background-color: #101010 !important;
|
||||
border-radius: 0px 0px 4px 4px;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
|
||||
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
height: 100%;
|
||||
background-color: #00A4DC;
|
||||
transition: width 0.2s ease-in-out;
|
||||
border-radius: 0px 0px 0px 4px;
|
||||
border-radius: 0px 0px 0px 8px;
|
||||
}
|
||||
|
||||
.card-banner {
|
||||
@@ -66,12 +66,12 @@
|
||||
grid-column: 2 / 3;
|
||||
backdrop-filter: blur(1px);
|
||||
background-color: rgb(0, 0, 0, 0.6);
|
||||
border-radius: 0px 4px 0px 0px;
|
||||
border-radius: 0px 8px 0px 0px;
|
||||
}
|
||||
|
||||
|
||||
.card-banner-image {
|
||||
border-radius: 4px 0px 0px 0px;
|
||||
border-radius: 8px 0px 0px 0px;
|
||||
max-height: inherit;
|
||||
/* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 520px));
|
||||
grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/
|
||||
/* margin-right: 20px; */
|
||||
margin-top: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.stat-card{
|
||||
border: 0 !important;
|
||||
@@ -12,7 +12,7 @@
|
||||
color: white;
|
||||
max-width: 500px;
|
||||
max-height: 180px;
|
||||
border-radius: 4px !important;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.stat-card-banner
|
||||
@@ -71,7 +71,7 @@
|
||||
color: white;
|
||||
display: flex;
|
||||
background-color: rgb(100, 100, 100,0.3);
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
font-size: 1.2em;
|
||||
align-self: flex-end;
|
||||
justify-content: space-evenly;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
background-color:rgba(100,100,100,0.2);
|
||||
padding:10px;
|
||||
|
||||
border-radius:4px;
|
||||
border-radius:8px;
|
||||
/* text-align: center; */
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
background-color: rgb(100, 100, 100,0.2);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -124,7 +124,7 @@ td:first-child {
|
||||
color: white;
|
||||
display: flex;
|
||||
background-color: rgb(100, 100, 100,0.3);
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
font-size: 1.2em;
|
||||
align-self: flex-end;
|
||||
justify-content: space-between;
|
||||
@@ -136,7 +136,7 @@ td:first-child {
|
||||
height: 35px;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
background-color: rgb(255, 255, 255, 0.1);
|
||||
color:white;
|
||||
font-size: 1em;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.console-message {
|
||||
|
||||
Reference in New Issue
Block a user