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:
Thomas Camlong committed 2026-08-09 00:59:41 +02:00
1 parent 84dcbd8847
commit b3f58cd8aa
23 files changed
+90 -883

No files matched your search

-51
View File
@@ -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());
};
-19
View File
@@ -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"));
});
});
-8
View File
@@ -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;
+1 -108
View File
@@ -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 };
}),
});
+1 -58
View File
@@ -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();
}),
});
-23
View File
@@ -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);
}),
});