فهرست منبع

RHL-006-refactor(caching): implement dynamic caching strategy for API routes

Code_Uwe 8 ساعت پیش
والد
کامیت
3f186c1748

+ 12 - 0
app/api/auth/login/route.js

@@ -9,6 +9,18 @@ import {
 	unauthorized,
 } from "@/lib/api/errors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * POST /api/auth/login
  *

+ 5 - 1
app/api/auth/login/route.test.js

@@ -28,7 +28,7 @@ import { getDb } from "@/lib/db";
 import User from "@/models/user";
 import { createSession } from "@/lib/auth/session";
 import { compare as bcryptCompare } from "bcryptjs";
-import { POST } from "./route";
+import { POST, dynamic } from "./route.js";
 
 function createRequestStub(body) {
 	return {
@@ -44,6 +44,10 @@ describe("POST /api/auth/login", () => {
 		getDb.mockResolvedValue({}); // we only need it to "connect"
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("logs in successfully with correct credentials", async () => {
 		const user = {
 			_id: "507f1f77bcf86cd799439011",

+ 12 - 0
app/api/auth/logout/route.js

@@ -1,6 +1,18 @@
 import { destroySession } from "@/lib/auth/session";
 import { withErrorHandling, json } from "@/lib/api/errors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/auth/logout
  *

+ 5 - 1
app/api/auth/logout/route.test.js

@@ -7,13 +7,17 @@ vi.mock("@/lib/auth/session", () => ({
 
 // 2) Import after mock
 import { destroySession } from "@/lib/auth/session";
-import { GET } from "./route";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/auth/logout", () => {
 	beforeEach(() => {
 		vi.clearAllMocks();
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("calls destroySession and returns ok: true", async () => {
 		const response = await GET();
 		const json = await response.json();

+ 12 - 0
app/api/branches/[branch]/[year]/[month]/days/route.js

@@ -10,6 +10,18 @@ import {
 } from "@/lib/api/errors";
 import { mapStorageReadError } from "@/lib/api/storageErrors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/branches/[branch]/[year]/[month]/days
  *

+ 5 - 1
app/api/branches/[branch]/[year]/[month]/days/route.test.js

@@ -10,7 +10,7 @@ vi.mock("@/lib/auth/session", () => ({
 }));
 
 import { getSession } from "@/lib/auth/session";
-import { GET } from "./route.js";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/branches/[branch]/[year]/[month]/days", () => {
 	let tmpRoot;
@@ -31,6 +31,10 @@ describe("GET /api/branches/[branch]/[year]/[month]/days", () => {
 		if (tmpRoot) await fs.rm(tmpRoot, { recursive: true, force: true });
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("returns 401 when unauthenticated", async () => {
 		getSession.mockResolvedValue(null);
 

+ 12 - 0
app/api/branches/[branch]/[year]/months/route.js

@@ -10,6 +10,18 @@ import {
 } from "@/lib/api/errors";
 import { mapStorageReadError } from "@/lib/api/storageErrors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/branches/[branch]/[year]/months
  *

+ 5 - 1
app/api/branches/[branch]/[year]/months/route.test.js

@@ -10,7 +10,7 @@ vi.mock("@/lib/auth/session", () => ({
 }));
 
 import { getSession } from "@/lib/auth/session";
-import { GET } from "./route.js";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/branches/[branch]/[year]/months", () => {
 	let tmpRoot;
@@ -31,6 +31,10 @@ describe("GET /api/branches/[branch]/[year]/months", () => {
 		if (tmpRoot) await fs.rm(tmpRoot, { recursive: true, force: true });
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("returns 401 when unauthenticated", async () => {
 		getSession.mockResolvedValue(null);
 

+ 12 - 0
app/api/branches/[branch]/years/route.js

@@ -10,6 +10,18 @@ import {
 } from "@/lib/api/errors";
 import { mapStorageReadError } from "@/lib/api/storageErrors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/branches/[branch]/years
  *

+ 5 - 1
app/api/branches/[branch]/years/route.test.js

@@ -10,7 +10,7 @@ vi.mock("@/lib/auth/session", () => ({
 }));
 
 import { getSession } from "@/lib/auth/session";
-import { GET } from "./route.js";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/branches/[branch]/years", () => {
 	let tmpRoot;
@@ -30,6 +30,10 @@ describe("GET /api/branches/[branch]/years", () => {
 		if (tmpRoot) await fs.rm(tmpRoot, { recursive: true, force: true });
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("returns 401 when unauthenticated", async () => {
 		getSession.mockResolvedValue(null);
 

+ 12 - 0
app/api/branches/route.js

@@ -8,6 +8,18 @@ import {
 	ApiError,
 } from "@/lib/api/errors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/branches
  *

+ 5 - 1
app/api/branches/route.test.js

@@ -10,7 +10,7 @@ vi.mock("@/lib/auth/session", () => ({
 }));
 
 import { getSession } from "@/lib/auth/session";
-import { GET } from "./route.js";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/branches", () => {
 	let tmpRoot;
@@ -29,6 +29,10 @@ describe("GET /api/branches", () => {
 		}
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("returns 401 when unauthenticated", async () => {
 		getSession.mockResolvedValue(null);
 

+ 12 - 0
app/api/files/route.js

@@ -10,6 +10,18 @@ import {
 } from "@/lib/api/errors";
 import { mapStorageReadError } from "@/lib/api/storageErrors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/files?branch=&year=&month=&day=
  *

+ 5 - 1
app/api/files/route.test.js

@@ -10,7 +10,7 @@ vi.mock("@/lib/auth/session", () => ({
 }));
 
 import { getSession } from "@/lib/auth/session";
-import { GET } from "./route.js";
+import { GET, dynamic } from "./route.js";
 
 describe("GET /api/files", () => {
 	let tmpRoot;
@@ -31,6 +31,10 @@ describe("GET /api/files", () => {
 		if (tmpRoot) await fs.rm(tmpRoot, { recursive: true, force: true });
 	});
 
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("returns 401 when unauthenticated", async () => {
 		getSession.mockResolvedValue(null);
 

+ 12 - 0
app/api/health/route.js

@@ -2,6 +2,18 @@ import { getDb } from "@/lib/db";
 import fs from "fs/promises";
 import { withErrorHandling, json } from "@/lib/api/errors";
 
+/**
+ * Next.js Route Handler caching configuration (RHL-006):
+ *
+ * We force this route to execute dynamically on every request.
+ *
+ * Reasons:
+ * - NAS contents can change at any time (new scans).
+ * - Auth/RBAC-protected responses must not be cached/shared across users.
+ * - We rely on a small storage-layer TTL micro-cache instead of Next route caching.
+ */
+export const dynamic = "force-dynamic";
+
 /**
  * GET /api/health
  *

+ 5 - 1
app/api/health/route.test.js

@@ -16,7 +16,7 @@ vi.mock("fs/promises", () => {
 	};
 });
 
-import { GET as getHealth } from "./route.js";
+import { GET as getHealth, dynamic } from "./route.js";
 import { getDb } from "@/lib/db";
 import fs from "fs/promises";
 
@@ -28,6 +28,10 @@ beforeEach(() => {
 });
 
 describe("GET /api/health", () => {
+	it('exports dynamic="force-dynamic" (RHL-006)', () => {
+		expect(dynamic).toBe("force-dynamic");
+	});
+
 	it("reports db=ok and nas with entries when both are healthy", async () => {
 		mockedGetDb.mockResolvedValue({
 			command: vi.fn().mockResolvedValue({ ok: 1 }),