mirror of
https://github.com/gethomepage/homepage.git
synced 2026-10-01 16:31:16 -07:00
caching
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import cache from "memory-cache";
|
||||||
|
|
||||||
import { parseFeed } from "./utils";
|
import { parseFeed } from "./utils";
|
||||||
|
|
||||||
import getServiceWidget from "utils/config/service-helpers";
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
@@ -6,6 +8,7 @@ import { sanitizeErrorURL } from "utils/proxy/api-helpers";
|
|||||||
import { httpProxy } from "utils/proxy/http";
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
|
||||||
const logger = createLogger("feedProxyHandler");
|
const logger = createLogger("feedProxyHandler");
|
||||||
|
const CACHE_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
export default async function feedProxyHandler(req, res) {
|
export default async function feedProxyHandler(req, res) {
|
||||||
const { group, service, index } = req.query;
|
const { group, service, index } = req.query;
|
||||||
@@ -22,6 +25,10 @@ export default async function feedProxyHandler(req, res) {
|
|||||||
return res.status(400).json({ error: "Invalid feed URL" });
|
return res.status(400).json({ error: "Invalid feed URL" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheKey = `feed:${url.href}`;
|
||||||
|
let items = cache.get(cacheKey);
|
||||||
|
|
||||||
|
if (!items) {
|
||||||
const [status, , data] = await httpProxy(url, {
|
const [status, , data] = await httpProxy(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": `gethomepage/${process.env.NEXT_PUBLIC_VERSION || "dev"}`,
|
"User-Agent": `gethomepage/${process.env.NEXT_PUBLIC_VERSION || "dev"}`,
|
||||||
@@ -34,7 +41,6 @@ export default async function feedProxyHandler(req, res) {
|
|||||||
return res.status(status).json({ error: { message: "HTTP Error", url: sanitizeErrorURL(url) } });
|
return res.status(status).json({ error: { message: "HTTP Error", url: sanitizeErrorURL(url) } });
|
||||||
}
|
}
|
||||||
|
|
||||||
let items;
|
|
||||||
try {
|
try {
|
||||||
items = parseFeed(Buffer.from(data).toString(), url.href);
|
items = parseFeed(Buffer.from(data).toString(), url.href);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -42,6 +48,10 @@ export default async function feedProxyHandler(req, res) {
|
|||||||
return res.status(500).json({ error: { message: "Invalid feed", url: sanitizeErrorURL(url) } });
|
return res.status(500).json({ error: { message: "Invalid feed", url: sanitizeErrorURL(url) } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only successful fetches are cached
|
||||||
|
cache.put(cacheKey, items, CACHE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
const maxItems = parseInt(widget.maxItems, 10) || 5;
|
const maxItems = parseInt(widget.maxItems, 10) || 5;
|
||||||
const showImages = widget.images !== false && widget.images !== "false";
|
const showImages = widget.images !== false && widget.images !== "false";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import cache from "memory-cache";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import createMockRes from "test-utils/create-mock-res";
|
import createMockRes from "test-utils/create-mock-res";
|
||||||
@@ -36,6 +37,7 @@ const req = { query: { group: "g", service: "svc", index: "0" } };
|
|||||||
describe("widgets/feed/proxy", () => {
|
describe("widgets/feed/proxy", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
cache.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when the url is missing or invalid", async () => {
|
it("returns 400 when the url is missing or invalid", async () => {
|
||||||
@@ -110,4 +112,37 @@ describe("widgets/feed/proxy", () => {
|
|||||||
expect(res.statusCode).toBe(500);
|
expect(res.statusCode).toBe(500);
|
||||||
expect(res.body).toEqual({ error: { message: "Invalid feed", url: "example.com (see logs for details)" } });
|
expect(res.body).toEqual({ error: { message: "Invalid feed", url: "example.com (see logs for details)" } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shares one cached fetch across widgets with different options", async () => {
|
||||||
|
getServiceWidget
|
||||||
|
.mockResolvedValueOnce({ type: "feed", url: "https://example.com/feed.xml", maxItems: 1 })
|
||||||
|
.mockResolvedValueOnce({ type: "feed", url: "https://example.com/feed.xml", images: false });
|
||||||
|
httpProxy.mockResolvedValue([200, "application/rss+xml", Buffer.from(feed)]);
|
||||||
|
|
||||||
|
const first = createMockRes();
|
||||||
|
await feedProxyHandler(req, first);
|
||||||
|
const second = createMockRes();
|
||||||
|
await feedProxyHandler(req, second);
|
||||||
|
|
||||||
|
expect(httpProxy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(first.body.items).toHaveLength(1);
|
||||||
|
expect(second.body.items).toHaveLength(5);
|
||||||
|
expect(second.body.items[0].image).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not cache failures", async () => {
|
||||||
|
getServiceWidget.mockResolvedValue({ type: "feed", url: "https://example.com/feed.xml" });
|
||||||
|
httpProxy
|
||||||
|
.mockResolvedValueOnce([404, "text/html", Buffer.from("Not found")])
|
||||||
|
.mockResolvedValueOnce([200, "application/rss+xml", Buffer.from(feed)]);
|
||||||
|
|
||||||
|
const failed = createMockRes();
|
||||||
|
await feedProxyHandler(req, failed);
|
||||||
|
const recovered = createMockRes();
|
||||||
|
await feedProxyHandler(req, recovered);
|
||||||
|
|
||||||
|
expect(failed.statusCode).toBe(404);
|
||||||
|
expect(recovered.statusCode).toBe(200);
|
||||||
|
expect(httpProxy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user