Statistics page redesign #60
@@ -159,7 +159,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchCompletionModalContent() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/completion-modal', { method: 'GET' });
|
||||
const res = await fetch('/api/settings/completion-modal', { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch completion modal');
|
||||
const data = await res.json();
|
||||
return {
|
||||
@@ -217,7 +217,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchActiveYear() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/year', { method: 'GET' });
|
||||
const res = await fetch('/api/settings/year', { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch active year');
|
||||
const data = await res.json();
|
||||
const year = Number(data?.year);
|
||||
@@ -229,7 +229,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchOscarEffectiveDate(year) {
|
||||
try {
|
||||
const res = await fetch(`/api/settings/oscar-date?year=${encodeURIComponent(String(year))}`, { method: 'GET' });
|
||||
const res = await fetch(`/api/settings/oscar-date?year=${encodeURIComponent(String(year))}`, { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch oscar date');
|
||||
const data = await res.json();
|
||||
const effectiveDate = typeof data?.effectiveDate === 'string' ? data.effectiveDate : null;
|
||||
@@ -247,7 +247,8 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
async function fetchMoviesSummary(year) {
|
||||
const res = await fetch(`/api/movies/summary?year=${encodeURIComponent(String(year))}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch movies summary');
|
||||
return await res.json();
|
||||
@@ -346,7 +347,8 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
fetchCompletionModalContent(),
|
||||
fetch(`/api/movies?year=${encodeURIComponent(String(activeYear))}&view=checklist`, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -498,7 +500,8 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
const updatedWatchedMoviesRes = await fetch('/api/movies/watchedMovies', {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (updatedWatchedMoviesRes.ok) {
|
||||
|
||||
@@ -162,7 +162,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchActiveYear() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/year', { method: 'GET' });
|
||||
const res = await fetch('/api/settings/year', { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch active year');
|
||||
const data = await res.json();
|
||||
const year = Number(data?.year);
|
||||
@@ -174,7 +174,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchOscarEffectiveDate(year) {
|
||||
try {
|
||||
const res = await fetch(`/api/settings/oscar-date?year=${encodeURIComponent(String(year))}`, { method: 'GET' });
|
||||
const res = await fetch(`/api/settings/oscar-date?year=${encodeURIComponent(String(year))}`, { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch oscar date');
|
||||
const data = await res.json();
|
||||
const effectiveDate = typeof data?.effectiveDate === 'string' ? data.effectiveDate : null;
|
||||
@@ -194,6 +194,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
const res = await fetch(`/api/movies/summary?year=${encodeURIComponent(String(year))}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch movies summary');
|
||||
return await res.json();
|
||||
@@ -319,6 +320,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
const res = await fetch(`/api/movies?year=${encodeURIComponent(String(activeYear))}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -130,7 +130,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchActiveYear() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/year', { method: 'GET' });
|
||||
const res = await fetch('/api/settings/year', { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch active year');
|
||||
const data = await res.json();
|
||||
const year = Number(data?.year);
|
||||
@@ -142,7 +142,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
|
||||
async function fetchWinners() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/winners', { method: 'GET' });
|
||||
const res = await fetch('/api/settings/winners', { method: 'GET', cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch winners');
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return Array.isArray(data?.winners) ? data.winners : [];
|
||||
@@ -155,7 +155,8 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
try {
|
||||
const res = await fetch('/api/users/completions', {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch completions');
|
||||
const data = await res.json().catch(() => ({}));
|
||||
@@ -650,7 +651,8 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
const [statsRes, winners, completions] = await Promise.all([
|
||||
fetch(statsUrl, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-cache',
|
||||
}),
|
||||
fetchWinners(),
|
||||
fetchCompletions(),
|
||||
|
||||
@@ -291,6 +291,9 @@ router.get("/watchedMovies", async (req, res) => {
|
||||
const user = await User.findById(decoded.id).select("watchedMovies").lean(); // Get user by ID
|
||||
if (!user) return res.status(404).json({ message: "User not found" });
|
||||
|
||||
// User-specific: never allow shared/proxy caching.
|
||||
res.set("Cache-Control", "private, no-store, must-revalidate");
|
||||
res.set("Vary", "Authorization");
|
||||
res.status(200).json(user.watchedMovies); // Return the list of watched movies
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
@@ -122,6 +122,10 @@ async function getOrInitActiveYear() {
|
||||
// Get user stats with watched movie titles and details from the movies collection
|
||||
router.get("/stats", verifyToken, async (req, res) => {
|
||||
try {
|
||||
// Auth-scoped response: never allow caching.
|
||||
res.set("Cache-Control", "private, no-store, must-revalidate");
|
||||
res.set("Vary", "Authorization");
|
||||
|
||||
// Fetch all users (only needed fields)
|
||||
const users = await User.find().select("name watchedMovies").lean();
|
||||
|
||||
@@ -192,6 +196,10 @@ router.get("/stats", verifyToken, async (req, res) => {
|
||||
// Returns: { years: number[], totals: { [year]: number }, completersByYear: { [year]: {name, watchedCount, totalMoviesCount}[] } }
|
||||
router.get("/completions", verifyToken, async (req, res) => {
|
||||
try {
|
||||
// Auth-scoped response: never allow caching.
|
||||
res.set("Cache-Control", "private, no-store, must-revalidate");
|
||||
res.set("Vary", "Authorization");
|
||||
|
||||
const yearsRaw = await Movie.distinct("year");
|
||||
const years = (Array.isArray(yearsRaw) ? yearsRaw : [])
|
||||
.map((y) => Number(y))
|
||||
|
||||
Reference in New Issue
Block a user