diff --git a/public/js/app_checklist.js b/public/js/app_checklist.js index 5823cdb..77e4d54 100644 --- a/public/js/app_checklist.js +++ b/public/js/app_checklist.js @@ -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) { diff --git a/public/js/app_movies.js b/public/js/app_movies.js index 3322ae4..b3aea54 100644 --- a/public/js/app_movies.js +++ b/public/js/app_movies.js @@ -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) { diff --git a/public/js/app_user-stat.js b/public/js/app_user-stat.js index f95f26b..20ab98e 100644 --- a/public/js/app_user-stat.js +++ b/public/js/app_user-stat.js @@ -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(), diff --git a/routes/movies.js b/routes/movies.js index 9ed1125..6a80f7d 100644 --- a/routes/movies.js +++ b/routes/movies.js @@ -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 }); diff --git a/routes/users.js b/routes/users.js index 0c3aa29..8c0ec0a 100644 --- a/routes/users.js +++ b/routes/users.js @@ -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))