perf: drop the cache layers that measured at zero
Ablation (PHASE2-FINDINGS.md) attributed the memory win to the serverExternalPackages line and the integrations barrel diet. These three measured at exactly zero and were most of the file churn, so they go: next-cache-handler: orphaned. cacheHandlers and cacheComponents are both already gone from next.config.ts and nothing imported the package — it was reachable only through a stale dependency entry. cache-tags / cache-invalidation: there is no "use cache", cacheTag, cacheLife or unstable_cache anywhere in app source, so revalidateTag was firing on tags nothing registers, with no handler to receive them, inside a catch that swallowed the resulting out-of-scope error. Removing the calls also removed the SELECTs that existed only to supply a board name or member list to them, so board.ts, group.ts, user.ts, serverSettings.ts, section-router.ts and change-search-preferences.ts return to the merge-base exactly. The live in-process integration data cache is untouched. Redis 6 -> 2 client consolidation: no measurable effect at idle (139.3 vs 139.2 MiB) or under load. Reverted, but the pubSub:last:* TTL is kept — that one is an unbounded-growth fix, not a consolidation. 23 files, -883 lines. Diff vs merge-base: 100 files/3336 insertions -> 82 files/2484. pnpm test 144/144 files, 1795 tests, 0 failures; typecheck and lint clean; app and Docker image build.
This commit is contained in:
23 files changed
+90
-883
No files matched your search
@@ -1,51 +0,0 @@
|
||||
// ponytail: invalidation is wired now; `cacheTag()` on RSC read paths is Phase 1 follow-up.
|
||||
// Until reads use `"use cache"` + `cacheTag()`, these calls affect only Next.js-internal
|
||||
// cached segments (layout shells, static params) via cacheComponents, not cross-request DB queries.
|
||||
//
|
||||
// Two-tier invalidation:
|
||||
// - `invalidateIntegrationCache`: for integration CONFIG changes (CRUD, permissions).
|
||||
// Clears module cache + Next tags + Redis session store.
|
||||
// - `invalidateIntegrationDataCache` (from integration-data-cache.ts): for integration DATA changes
|
||||
// (widget interact mutations). Clears module cache only — pair with requestHandler.invalidateCache().
|
||||
|
||||
import { cacheTags } from "./cache-tags";
|
||||
import { invalidateIntegrationDataCache } from "./integration-data-cache";
|
||||
|
||||
// ponytail: revalidateTag throws outside Next.js request context (tests, tasks, websocket).
|
||||
// Ceiling: if Next.js context detection changes, update the require/catch.
|
||||
function isExpectedRevalidateError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "";
|
||||
return message.includes("static generation") || message.includes("was called outside a request scope");
|
||||
}
|
||||
|
||||
function safeRevalidateTag(tag: string) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { revalidateTag } = require("next/cache") as typeof import("next/cache");
|
||||
revalidateTag(tag, "max");
|
||||
} catch (error) {
|
||||
if (!isExpectedRevalidateError(error)) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`[cache-invalidation] revalidateTag("${tag}") failed:`, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const invalidateBoardCache = (boardId: string, boardName?: string) => {
|
||||
safeRevalidateTag(cacheTags.board(boardId));
|
||||
if (boardName) safeRevalidateTag(cacheTags.boardByName(boardName));
|
||||
safeRevalidateTag(cacheTags.boardList());
|
||||
};
|
||||
|
||||
export const invalidateIntegrationCache = (integrationId: string) => {
|
||||
invalidateIntegrationDataCache(integrationId);
|
||||
safeRevalidateTag(cacheTags.integration(integrationId));
|
||||
};
|
||||
|
||||
export const invalidateUserCache = (userId: string) => {
|
||||
safeRevalidateTag(cacheTags.user(userId));
|
||||
};
|
||||
|
||||
export const invalidateServerSettingsCache = () => {
|
||||
safeRevalidateTag(cacheTags.serverSettings());
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { cacheTags } from "./cache-tags";
|
||||
|
||||
describe("cacheTags", () => {
|
||||
it("produces stable tag shapes", () => {
|
||||
expect(cacheTags.serverSettings()).toBe("server-settings");
|
||||
expect(cacheTags.board("abc")).toBe("board:abc");
|
||||
expect(cacheTags.boardByName("My Board")).toBe("board-name:MY BOARD");
|
||||
expect(cacheTags.boardList()).toBe("board-list");
|
||||
expect(cacheTags.user("u1")).toBe("user:u1");
|
||||
expect(cacheTags.integration("i1")).toBe("integration:i1");
|
||||
});
|
||||
|
||||
it("boardByName uppercases consistently", () => {
|
||||
expect(cacheTags.boardByName("home")).toBe(cacheTags.boardByName("HOME"));
|
||||
expect(cacheTags.boardByName("Home")).toBe(cacheTags.boardByName("hOmE"));
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
export const cacheTags = {
|
||||
serverSettings: () => "server-settings" as const,
|
||||
board: (boardId: string) => `board:${boardId}` as const,
|
||||
boardByName: (name: string) => `board-name:${name.toUpperCase()}` as const,
|
||||
boardList: () => "board-list" as const,
|
||||
user: (userId: string) => `user:${userId}` as const,
|
||||
integration: (integrationId: string) => `integration:${integrationId}` as const,
|
||||
} as const;
|
||||
@@ -59,7 +59,6 @@ import { zodUnionFromArray } from "@homarr/validation/enums";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||
import { sectionSchema, sharedItemSchema } from "@homarr/validation/shared";
|
||||
|
||||
import { invalidateBoardCache, invalidateUserCache } from "../cache-invalidation";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
||||
import { generateResponsiveGridFor } from "./board/grid-algorithm";
|
||||
@@ -326,11 +325,8 @@ export const boardRouter = createTRPCRouter({
|
||||
|
||||
if (!user?.homeBoardId) {
|
||||
await ctx.db.update(users).set({ homeBoardId: boardId }).where(eq(users.id, ctx.session.user.id));
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}
|
||||
|
||||
invalidateBoardCache(boardId, input.name);
|
||||
|
||||
return { boardId };
|
||||
}),
|
||||
duplicateBoard: permissionRequiredProcedure
|
||||
@@ -559,8 +555,6 @@ export const boardRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
invalidateBoardCache(newBoardId, input.name);
|
||||
|
||||
return { boardId: newBoardId };
|
||||
}),
|
||||
renameBoard: protectedProcedure
|
||||
@@ -579,17 +573,7 @@ export const boardRouter = createTRPCRouter({
|
||||
|
||||
await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]);
|
||||
|
||||
const existingBoard = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.id),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id));
|
||||
|
||||
invalidateBoardCache(input.id, input.name);
|
||||
if (existingBoard && existingBoard.name !== input.name) {
|
||||
invalidateBoardCache(input.id, existingBoard.name);
|
||||
}
|
||||
}),
|
||||
changeBoardVisibility: protectedProcedure
|
||||
.meta({
|
||||
@@ -604,12 +588,6 @@ export const boardRouter = createTRPCRouter({
|
||||
.output(z.void())
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
|
||||
const existingBoard = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.id),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
|
||||
if (
|
||||
@@ -626,8 +604,6 @@ export const boardRouter = createTRPCRouter({
|
||||
.update(boards)
|
||||
.set({ isPublic: input.visibility === "public" })
|
||||
.where(eq(boards.id, input.id));
|
||||
|
||||
invalidateBoardCache(input.id, existingBoard?.name);
|
||||
}),
|
||||
deleteBoard: protectedProcedure
|
||||
.meta({
|
||||
@@ -643,22 +619,7 @@ export const boardRouter = createTRPCRouter({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
|
||||
const existingBoard = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.id),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
const affectedUsers = await ctx.db.query.users.findMany({
|
||||
columns: { id: true },
|
||||
where: eq(users.homeBoardId, input.id),
|
||||
});
|
||||
|
||||
await ctx.db.delete(boards).where(eq(boards.id, input.id));
|
||||
|
||||
for (const user of affectedUsers) {
|
||||
invalidateUserCache(user.id);
|
||||
}
|
||||
invalidateBoardCache(input.id, existingBoard?.name);
|
||||
}),
|
||||
setHomeBoard: protectedProcedure
|
||||
.meta({
|
||||
@@ -675,8 +636,6 @@ export const boardRouter = createTRPCRouter({
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
|
||||
|
||||
await ctx.db.update(users).set({ homeBoardId: input.id }).where(eq(users.id, ctx.session.user.id));
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
setMobileHomeBoard: protectedProcedure
|
||||
.meta({
|
||||
@@ -693,8 +652,6 @@ export const boardRouter = createTRPCRouter({
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
|
||||
|
||||
await ctx.db.update(users).set({ mobileHomeBoardId: input.id }).where(eq(users.id, ctx.session.user.id));
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
getHomeBoard: publicProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.session?.user.id;
|
||||
@@ -849,8 +806,6 @@ export const boardRouter = createTRPCRouter({
|
||||
if (removedLayoutIds.length > 0) {
|
||||
await ctx.db.delete(layouts).where(inArray(layouts.id, removedLayoutIds));
|
||||
}
|
||||
|
||||
invalidateBoardCache(board.id, board.name);
|
||||
}),
|
||||
savePartialBoardSettings: protectedProcedure
|
||||
.meta({
|
||||
@@ -866,11 +821,6 @@ export const boardRouter = createTRPCRouter({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||
|
||||
const existingBoard = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.id),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
await ctx.db
|
||||
.update(boards)
|
||||
.set({
|
||||
@@ -900,8 +850,6 @@ export const boardRouter = createTRPCRouter({
|
||||
disableStatus: input.disableStatus,
|
||||
})
|
||||
.where(eq(boards.id, input.id));
|
||||
|
||||
invalidateBoardCache(input.id, existingBoard?.name);
|
||||
}),
|
||||
saveBoard: protectedProcedure.input(boardSaveSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||
@@ -1318,8 +1266,6 @@ export const boardRouter = createTRPCRouter({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
invalidateBoardCache(dbBoard.id, dbBoard.name);
|
||||
}),
|
||||
getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
@@ -1396,16 +1342,6 @@ export const boardRouter = createTRPCRouter({
|
||||
saveUserBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
|
||||
const board = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.entityId),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
const existingUserPermissions = await ctx.db.query.boardUserPermissions.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(boardUserPermissions.boardId, input.entityId),
|
||||
});
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
@@ -1441,31 +1377,10 @@ export const boardRouter = createTRPCRouter({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = [
|
||||
...new Set([
|
||||
...existingUserPermissions.map((permission) => permission.userId),
|
||||
...input.permissions.map((permission) => permission.principalId),
|
||||
]),
|
||||
];
|
||||
for (const userId of userIds) {
|
||||
invalidateUserCache(userId);
|
||||
}
|
||||
invalidateBoardCache(input.entityId, board?.name);
|
||||
}),
|
||||
saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
|
||||
const board = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.entityId),
|
||||
columns: { name: true },
|
||||
});
|
||||
|
||||
const existingGroupPermissions = await ctx.db.query.boardGroupPermissions.findMany({
|
||||
columns: { groupId: true },
|
||||
where: eq(boardGroupPermissions.boardId, input.entityId),
|
||||
});
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
@@ -1503,23 +1418,6 @@ export const boardRouter = createTRPCRouter({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const groupIds = [
|
||||
...new Set([
|
||||
...existingGroupPermissions.map((permission) => permission.groupId),
|
||||
...input.permissions.map((permission) => permission.principalId),
|
||||
]),
|
||||
];
|
||||
if (groupIds.length > 0) {
|
||||
const groupMembersForBoard = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: inArray(groupMembers.groupId, groupIds),
|
||||
});
|
||||
for (const member of groupMembersForBoard) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}
|
||||
invalidateBoardCache(input.entityId, board?.name);
|
||||
}),
|
||||
importOldmarrConfig: permissionRequiredProcedure
|
||||
.requiresPermission("board-create")
|
||||
@@ -1527,10 +1425,7 @@ export const boardRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const content = await input.file.text();
|
||||
const oldmarr = oldmarrConfigSchema.parse(JSON.parse(content));
|
||||
const importedBoards = await importOldmarrAsync(ctx.db, oldmarr, input.configuration);
|
||||
for (const board of importedBoards) {
|
||||
invalidateBoardCache(board.id, board.name);
|
||||
}
|
||||
await importOldmarrAsync(ctx.db, oldmarr, input.configuration);
|
||||
}),
|
||||
addItem: protectedProcedure
|
||||
.meta({
|
||||
@@ -1659,8 +1554,6 @@ export const boardRouter = createTRPCRouter({
|
||||
.values(input.integrationIds.map((integrationId) => ({ itemId, integrationId })));
|
||||
}
|
||||
|
||||
invalidateBoardCache(input.boardId, board.name);
|
||||
|
||||
return { itemId };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from "zod/v4";
|
||||
import { canManageGroupMembersLocally, isGroupMembershipManagedLocally } from "@homarr/auth/server";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync, inArray, like, not } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db";
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
} from "@homarr/validation/group";
|
||||
|
||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, protectedProcedure } from "../trpc";
|
||||
import { invalidateUserCache } from "../cache-invalidation";
|
||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||
|
||||
export const groupRouter = createTRPCRouter({
|
||||
@@ -232,14 +231,6 @@ export const groupRouter = createTRPCRouter({
|
||||
name: input.name,
|
||||
})
|
||||
.where(eq(groups.id, input.id));
|
||||
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(groupMembers.groupId, input.id),
|
||||
});
|
||||
for (const member of members) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}),
|
||||
savePartialSettings: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -254,14 +245,6 @@ export const groupRouter = createTRPCRouter({
|
||||
mobileHomeBoardId: input.settings.mobileHomeBoardId,
|
||||
})
|
||||
.where(eq(groups.id, input.id));
|
||||
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(groupMembers.groupId, input.id),
|
||||
});
|
||||
for (const member of members) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}),
|
||||
savePositions: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -285,14 +268,6 @@ export const groupRouter = createTRPCRouter({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: inArray(groupMembers.groupId, input.positions),
|
||||
});
|
||||
for (const userId of new Set(members.map((member) => member.userId))) {
|
||||
invalidateUserCache(userId);
|
||||
}
|
||||
}),
|
||||
savePermissions: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -300,11 +275,6 @@ export const groupRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(groupMembers.groupId, input.groupId),
|
||||
});
|
||||
|
||||
await ctx.db.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId));
|
||||
|
||||
if (input.permissions.length > 0) {
|
||||
@@ -315,10 +285,6 @@ export const groupRouter = createTRPCRouter({
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
for (const member of members) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}),
|
||||
transferOwnership: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -327,22 +293,12 @@ export const groupRouter = createTRPCRouter({
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
|
||||
const group = await ctx.db.query.groups.findFirst({
|
||||
columns: { ownerId: true },
|
||||
where: eq(groups.id, input.groupId),
|
||||
});
|
||||
|
||||
await ctx.db
|
||||
.update(groups)
|
||||
.set({
|
||||
ownerId: input.userId,
|
||||
})
|
||||
.where(eq(groups.id, input.groupId));
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
if (group?.ownerId && group.ownerId !== input.userId) {
|
||||
invalidateUserCache(group.ownerId);
|
||||
}
|
||||
}),
|
||||
deleteGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -351,16 +307,7 @@ export const groupRouter = createTRPCRouter({
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(groupMembers.groupId, input.id),
|
||||
});
|
||||
|
||||
await ctx.db.delete(groups).where(eq(groups.id, input.id));
|
||||
|
||||
for (const member of members) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}),
|
||||
addMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -392,8 +339,6 @@ export const groupRouter = createTRPCRouter({
|
||||
groupId: input.groupId,
|
||||
userId: input.userId,
|
||||
});
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
removeMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -406,8 +351,6 @@ export const groupRouter = createTRPCRouter({
|
||||
await ctx.db
|
||||
.delete(groupMembers)
|
||||
.where(and(eq(groupMembers.groupId, input.groupId), eq(groupMembers.userId, input.userId)));
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/validation/widgets/media-request";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { invalidateIntegrationCache, invalidateUserCache } from "../../cache-invalidation";
|
||||
import { invalidateIntegrationDataCache } from "../../integration-data-cache";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "./integration-access";
|
||||
@@ -374,7 +373,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
await invalidateIntegrationCacheAsync(integrationId);
|
||||
invalidateIntegrationCache(integrationId);
|
||||
invalidateIntegrationDataCache(integrationId);
|
||||
}),
|
||||
update: protectedProcedure.input(integrationUpdateSchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
@@ -481,7 +480,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
// Invalidate all cached data for this integration so that widgets pick up the
|
||||
// new configuration immediately instead of serving stale (or errored) data.
|
||||
await invalidateIntegrationCacheAsync(input.id);
|
||||
invalidateIntegrationCache(input.id);
|
||||
invalidateIntegrationDataCache(input.id);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.meta({
|
||||
@@ -506,7 +505,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
|
||||
// Clean up any cached data left behind by the deleted integration.
|
||||
await invalidateIntegrationCacheAsync(input.id);
|
||||
invalidateIntegrationCache(input.id);
|
||||
invalidateIntegrationDataCache(input.id);
|
||||
}),
|
||||
getIntegrationPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
@@ -585,11 +584,6 @@ export const integrationRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||
|
||||
const existingUserPermissions = await ctx.db.query.integrationUserPermissions.findMany({
|
||||
columns: { userId: true },
|
||||
where: eq(integrationUserPermissions.integrationId, input.entityId),
|
||||
});
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await ctx.db.transaction(async (transaction) => {
|
||||
@@ -631,27 +625,13 @@ export const integrationRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = [
|
||||
...new Set([
|
||||
...existingUserPermissions.map((permission) => permission.userId),
|
||||
...input.permissions.map((permission) => permission.principalId),
|
||||
]),
|
||||
];
|
||||
for (const userId of userIds) {
|
||||
invalidateUserCache(userId);
|
||||
}
|
||||
invalidateIntegrationCache(input.entityId);
|
||||
invalidateIntegrationDataCache(input.entityId);
|
||||
}),
|
||||
saveGroupIntegrationPermissions: protectedProcedure
|
||||
.input(integrationSavePermissionsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||
|
||||
const existingGroupPermissions = await ctx.db.query.integrationGroupPermissions.findMany({
|
||||
columns: { groupId: true },
|
||||
where: eq(integrationGroupPermissions.integrationId, input.entityId),
|
||||
});
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
@@ -693,22 +673,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
const groupIds = [
|
||||
...new Set([
|
||||
...existingGroupPermissions.map((permission) => permission.groupId),
|
||||
...input.permissions.map((permission) => permission.principalId),
|
||||
]),
|
||||
];
|
||||
if (groupIds.length > 0) {
|
||||
const members = await ctx.db.query.groupMembers.findMany({
|
||||
columns: { userId: true },
|
||||
where: inArray(groupMembers.groupId, groupIds),
|
||||
});
|
||||
for (const member of members) {
|
||||
invalidateUserCache(member.userId);
|
||||
}
|
||||
}
|
||||
invalidateIntegrationCache(input.entityId);
|
||||
invalidateIntegrationDataCache(input.entityId);
|
||||
}),
|
||||
searchInIntegration: protectedProcedure
|
||||
.meta({
|
||||
|
||||
@@ -4,7 +4,6 @@ import { z } from "zod/v4";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { sectionCollapseStates, sections } from "@homarr/db/schema";
|
||||
|
||||
import { invalidateUserCache } from "../../cache-invalidation";
|
||||
import { createTRPCRouter, protectedProcedure } from "../../trpc";
|
||||
|
||||
export const sectionRouter = createTRPCRouter({
|
||||
@@ -38,7 +37,6 @@ export const sectionRouter = createTRPCRouter({
|
||||
userId: ctx.session.user.id,
|
||||
collapsed: input.collapsed,
|
||||
});
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +48,5 @@ export const sectionRouter = createTRPCRouter({
|
||||
.where(
|
||||
and(eq(sectionCollapseStates.sectionId, section.id), eq(sectionCollapseStates.userId, ctx.session.user.id)),
|
||||
);
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
});
|
||||
@@ -14,7 +14,6 @@ import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||
|
||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc";
|
||||
import { invalidateServerSettingsCache } from "../cache-invalidation";
|
||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||
|
||||
const boardServerSettingsSchema = z.object({
|
||||
@@ -92,8 +91,6 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
await insertServerSettingByKeyAsync(ctx.db, "board", next);
|
||||
}
|
||||
|
||||
invalidateServerSettingsCache();
|
||||
|
||||
return next;
|
||||
}),
|
||||
saveSettings: permissionRequiredProcedure
|
||||
@@ -110,8 +107,6 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
...current,
|
||||
...input.value,
|
||||
} as ServerSettings[keyof ServerSettings]);
|
||||
|
||||
invalidateServerSettingsCache();
|
||||
}),
|
||||
initSettings: onboardingProcedure
|
||||
.requiresStep("settings")
|
||||
@@ -121,7 +116,5 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
await updateServerSettingByKeyAsync(ctx.db, "analytics", { ...currentAnalytics, ...input.analytics });
|
||||
await updateServerSettingByKeyAsync(ctx.db, "crawlingAndIndexing", input.crawlingAndIndexing);
|
||||
await nextOnboardingStepAsync(ctx.db, undefined);
|
||||
|
||||
invalidateServerSettingsCache();
|
||||
}),
|
||||
});
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
} from "@homarr/validation/user";
|
||||
|
||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||
import { invalidateUserCache } from "../cache-invalidation";
|
||||
import {
|
||||
createTRPCRouter,
|
||||
isDemoMode,
|
||||
@@ -222,8 +221,6 @@ export const userRouter = createTRPCRouter({
|
||||
image: input.image,
|
||||
})
|
||||
.where(eq(users.id, input.userId));
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
getAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
@@ -455,8 +452,6 @@ export const userRouter = createTRPCRouter({
|
||||
emailVerified: emailDirty === true ? null : undefined,
|
||||
})
|
||||
.where(eq(users.id, input.id));
|
||||
|
||||
invalidateUserCache(input.id);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ userId: z.string() }))
|
||||
@@ -480,7 +475,6 @@ export const userRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
await ctx.db.delete(users).where(eq(users.id, input.userId));
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
changePassword: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(userChangePasswordApiSchema))
|
||||
@@ -553,7 +547,6 @@ export const userRouter = createTRPCRouter({
|
||||
password: hashedPassword,
|
||||
})
|
||||
.where(eq(users.id, input.userId));
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
changeHomeBoards: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(userChangeHomeBoardsSchema.and(z.object({ userId: z.string() }))))
|
||||
@@ -605,8 +598,6 @@ export const userRouter = createTRPCRouter({
|
||||
mobileHomeBoardId: input.mobileHomeBoardId,
|
||||
})
|
||||
.where(eq(users.id, input.userId));
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
}),
|
||||
changeDefaultSearchEngine: protectedProcedure
|
||||
.input(
|
||||
@@ -662,8 +653,6 @@ export const userRouter = createTRPCRouter({
|
||||
colorScheme: input.colorScheme,
|
||||
})
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
changeEnableRightClickOnWidgets: protectedProcedure
|
||||
.meta({
|
||||
@@ -690,8 +679,6 @@ export const userRouter = createTRPCRouter({
|
||||
enableRightClickOnWidgets: input.enableRightClickOnWidgets,
|
||||
})
|
||||
.where(eq(users.id, input.id));
|
||||
|
||||
invalidateUserCache(input.id);
|
||||
}),
|
||||
changePingIconsEnabled: protectedProcedure
|
||||
.input(userPingIconsEnabledSchema.and(byIdSchema))
|
||||
@@ -710,8 +697,6 @@ export const userRouter = createTRPCRouter({
|
||||
pingIconsEnabled: input.pingIconsEnabled,
|
||||
})
|
||||
.where(eq(users.id, input.id));
|
||||
|
||||
invalidateUserCache(input.id);
|
||||
}),
|
||||
changeDdgBangs: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(userDdgBangsSchema.and(byIdSchema)))
|
||||
@@ -739,8 +724,6 @@ export const userRouter = createTRPCRouter({
|
||||
ddgBangs: input.ddgBangs,
|
||||
})
|
||||
.where(eq(users.id, input.id));
|
||||
|
||||
invalidateUserCache(input.id);
|
||||
}),
|
||||
changeFirstDayOfWeek: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(userFirstDayOfWeekSchema.and(byIdSchema)))
|
||||
@@ -782,8 +765,6 @@ export const userRouter = createTRPCRouter({
|
||||
firstDayOfWeek: input.firstDayOfWeek,
|
||||
})
|
||||
.where(eq(users.id, input.id));
|
||||
|
||||
invalidateUserCache(input.id);
|
||||
}),
|
||||
completeTour: protectedProcedure
|
||||
.input(z.object({ tour: z.enum(["manage", "board"]) }))
|
||||
@@ -794,8 +775,6 @@ export const userRouter = createTRPCRouter({
|
||||
} as const;
|
||||
|
||||
await ctx.db.update(users).set(columnByTour[input.tour]).where(eq(users.id, ctx.session.user.id));
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
resetTours: protectedProcedure.mutation(async ({ ctx }) => {
|
||||
await ctx.db
|
||||
@@ -805,8 +784,6 @@ export const userRouter = createTRPCRouter({
|
||||
completedBoardTour: false,
|
||||
})
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
|
||||
invalidateUserCache(ctx.session.user.id);
|
||||
}),
|
||||
getTourStatus: protectedProcedure.query(async ({ ctx }) => {
|
||||
if (isDemoMode) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import type { Database } from "@homarr/db";
|
||||
import { users } from "@homarr/db/schema";
|
||||
import { userChangeSearchPreferencesSchema } from "@homarr/validation/user";
|
||||
|
||||
import { invalidateUserCache } from "../../cache-invalidation";
|
||||
|
||||
export const changeSearchPreferencesInputSchema = userChangeSearchPreferencesSchema.and(
|
||||
z.object({ userId: z.string() }),
|
||||
);
|
||||
@@ -53,6 +51,4 @@ export const changeSearchPreferencesAsync = async (
|
||||
ddgBangs: input.ddgBangsEnabled,
|
||||
})
|
||||
.where(eq(users.id, input.userId));
|
||||
|
||||
invalidateUserCache(input.userId);
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import { getServerSettingsAsync } from "@homarr/db/queries";
|
||||
import { boards, items } from "@homarr/db/schema";
|
||||
|
||||
import type { WidgetOptionsSettings } from "../../../../widgets/src/definition";
|
||||
import { invalidateBoardCache } from "../../cache-invalidation";
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "../board/board-access";
|
||||
|
||||
@@ -52,11 +51,5 @@ export const optionsRouter = createTRPCRouter({
|
||||
.update(items)
|
||||
.set({ options: SuperJSON.stringify(options) })
|
||||
.where(eq(items.id, input.itemId));
|
||||
|
||||
const board = await ctx.db.query.boards.findFirst({
|
||||
where: eq(boards.id, input.boardId),
|
||||
columns: { name: true },
|
||||
});
|
||||
invalidateBoardCache(input.boardId, board?.name);
|
||||
}),
|
||||
});
|
||||
Reference in new issue
Block a user