fix: complete review hardening

This commit is contained in:
ajnart committed 2026-08-26 16:12:59 +02:00
1 parent 6f85e18065
commit 2449eb0bc7
13 files changed
+99 -64

No files matched your search

+21
View File
@@ -1,5 +1,16 @@
import type { QueryKey } from "@tanstack/react-query";
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
interface ForbiddenTrpcError {
data: { code: "FORBIDDEN" };
}
export const isTrpcForbiddenError = (error: unknown): error is ForbiddenTrpcError => {
if (!isRecord(error) || !isRecord(error.data)) return false;
return error.data.code === "FORBIDDEN";
};
// RSC-prefetched data must survive the hydration pass without an immediate
// duplicate fetch. Widget-specific intervals still control live refreshes.
export const queryCacheDefaultStaleTimeMs = 30_000;
@@ -8,6 +19,7 @@ export const queryCacheDefaultGcTimeMs = 1000 * 60 * 5;
export const queryCacheMetadataStaleTimeMs = 1000 * 60 * 5;
const widgetDataQueryPaths = new Set(["app.byId", "app.byIds", "docker.getContainers", "integration.byIds"]);
const persistableDashboardDataPaths = new Set(["widget.stockPrice.getPriceHistory"]);
// Beszel system stats are large time-series payloads and were deliberately kept out of
// the persisted cache in #6289; live stats come from an SSE subscription instead.
const excludedWidgetPaths = new Set(["widget.app.ping", "widget.beszel.getSystemStats"]);
@@ -66,3 +78,12 @@ export const isWidgetDataQueryKey = (queryKey: QueryKey) => {
return isWidgetDataTrpcPath(path.join("."));
};
// Persist only data that is both public and independent of Homarr access grants.
// Integration, app, Docker, board-backed, and location queries must refetch after reload.
export const isPersistableDashboardQueryKey = (queryKey: QueryKey) => {
const path = getTrpcPathFromQueryKey(queryKey);
if (!path) return false;
return persistableDashboardDataPaths.has(path.join("."));
};
@@ -87,14 +87,14 @@ export const dockerRouter = createTRPCRouter({
mcp: {
enabled: true,
description:
"Invalidate the cached Docker inventory so the next Docker query performs a fresh user-requested discovery. Requires admin permission.",
"Invalidate the local Docker inventory and publish a non-persistent Redis refresh event. Returns scope 'published' when Redis accepts the event or 'local' when only this replica is refreshed. Requires admin permission.",
},
})
.concat(dockerMiddleware())
.mutation(async () => {
resetDockerInventory();
const propagated = await publishDockerInventoryRefreshAsync();
if (propagated) return { scope: "all" as const };
if (propagated) return { scope: "published" as const };
return { scope: "local" as const };
}),
getContainers: permissionRequiredProcedure