From 4361c8dbe2201df938a0dd7e6b8cd0babef6f2e3 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Fri, 7 Aug 2026 19:14:02 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20address=20Bugbot=20review=20#3=20?= =?UTF-8?q?=E2=80=94=20mutation=20invalidation=20gaps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dual-layer cache invalidation on interact mutations (dns-hole, media-requests, smart-home, indexer-manager, anchor-notes) - importOldmarrConfig: invalidate created boards - createBoard: invalidateUserCache when auto-setting homeBoardId - group.savePositions: invalidate user caches for affected members - integration.requestMedia: invalidate integration data cache - user.delete: invalidate user cache --- packages/api/src/router/board.ts | 6 +++++- packages/api/src/router/group.ts | 10 +++++++++- .../api/src/router/integration/integration-router.ts | 5 ++++- packages/api/src/router/user.ts | 1 + packages/api/src/router/widgets/anchor-notes.ts | 2 ++ packages/api/src/router/widgets/dns-hole.ts | 2 ++ packages/api/src/router/widgets/indexer-manager.ts | 1 + packages/api/src/router/widgets/media-requests.ts | 2 ++ packages/api/src/router/widgets/smart-home.ts | 2 ++ .../old-import/src/import/import-single-oldmarr.ts | 2 ++ packages/old-import/src/index.ts | 2 +- 11 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index b425da89b..42422f8d7 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -326,6 +326,7 @@ 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); @@ -1497,7 +1498,10 @@ export const boardRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { const content = await input.file.text(); const oldmarr = oldmarrConfigSchema.parse(JSON.parse(content)); - await importOldmarrAsync(ctx.db, oldmarr, input.configuration); + const importedBoards = await importOldmarrAsync(ctx.db, oldmarr, input.configuration); + for (const board of importedBoards) { + invalidateBoardCache(board.id, board.name); + } }), addItem: protectedProcedure .meta({ diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts index f15741456..aef26559e 100644 --- a/packages/api/src/router/group.ts +++ b/packages/api/src/router/group.ts @@ -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, like, not } from "@homarr/db"; +import { and, eq, handleTransactionsAsync, inArray, 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"; @@ -277,6 +277,14 @@ 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") diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 0de72ff7d..204a7b682 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -38,6 +38,7 @@ import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/va 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"; import { MissingSecretError, testConnectionAsync } from "./integration-test-connection"; @@ -824,7 +825,9 @@ export const integrationRouter = createTRPCRouter({ .input(mediaRequestRequestSchema) .mutation(async ({ ctx, input }) => { const integration = await createIntegrationAsync(ctx.integration); - return await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons); + const result = await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons); + invalidateIntegrationDataCache(ctx.integration.id); + return result; }), }); diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index a93cd1711..e93519723 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -480,6 +480,7 @@ export const userRouter = createTRPCRouter({ } await ctx.db.delete(users).where(eq(users.id, input.userId)); + invalidateUserCache(input.userId); }), changePassword: protectedProcedure .input(convertIntersectionToZodObject(userChangePasswordApiSchema)) diff --git a/packages/api/src/router/widgets/anchor-notes.ts b/packages/api/src/router/widgets/anchor-notes.ts index 445b5b0c9..ea0937b92 100644 --- a/packages/api/src/router/widgets/anchor-notes.ts +++ b/packages/api/src/router/widgets/anchor-notes.ts @@ -77,6 +77,8 @@ export const anchorNotesRouter = createTRPCRouter({ }); invalidateIntegrationDataCache(ctx.integration.id); + anchorNoteRequestHandler.invalidateCache(); + anchorNotesListRequestHandler.invalidateCache(); return updatedNote; } catch (error) { if (error instanceof ResponseError && error.statusCode === 400) { diff --git a/packages/api/src/router/widgets/dns-hole.ts b/packages/api/src/router/widgets/dns-hole.ts index fde3dc291..6a076683c 100644 --- a/packages/api/src/router/widgets/dns-hole.ts +++ b/packages/api/src/router/widgets/dns-hole.ts @@ -46,6 +46,7 @@ export const dnsHoleRouter = createTRPCRouter({ const client = await createIntegrationAsync(integration); await client.enableAsync(); invalidateIntegrationDataCache(integration.id); + dnsHoleRequestHandler.invalidateCache(); }), disable: protectedProcedure @@ -66,5 +67,6 @@ export const dnsHoleRouter = createTRPCRouter({ const client = await createIntegrationAsync(integration); await client.disableAsync(input.duration); invalidateIntegrationDataCache(integration.id); + dnsHoleRequestHandler.invalidateCache(); }), }); diff --git a/packages/api/src/router/widgets/indexer-manager.ts b/packages/api/src/router/widgets/indexer-manager.ts index b735ac4e7..875b8991b 100644 --- a/packages/api/src/router/widgets/indexer-manager.ts +++ b/packages/api/src/router/widgets/indexer-manager.ts @@ -49,5 +49,6 @@ export const indexerManagerRouter = createTRPCRouter({ for (const integration of ctx.integrations) { invalidateIntegrationDataCache(integration.id); } + indexerManagerRequestHandler.invalidateCache(); }), }); diff --git a/packages/api/src/router/widgets/media-requests.ts b/packages/api/src/router/widgets/media-requests.ts index e24d2a2bd..513375937 100644 --- a/packages/api/src/router/widgets/media-requests.ts +++ b/packages/api/src/router/widgets/media-requests.ts @@ -109,5 +109,7 @@ export const mediaRequestsRouter = createTRPCRouter({ await answerActions[input.answer](input.requestId); invalidateIntegrationDataCache(integration.id); + mediaRequestListRequestHandler.invalidateCache(); + mediaRequestStatsRequestHandler.invalidateCache(); }), }); diff --git a/packages/api/src/router/widgets/smart-home.ts b/packages/api/src/router/widgets/smart-home.ts index 241c18d7f..48bf29d0e 100644 --- a/packages/api/src/router/widgets/smart-home.ts +++ b/packages/api/src/router/widgets/smart-home.ts @@ -43,6 +43,7 @@ export const smartHomeRouter = createTRPCRouter({ const success = await client.triggerToggleAsync(input.entityId); invalidateIntegrationDataCache(integration.id); + smartHomeEntityStateRequestHandler.invalidateCache(); return success; }), executeAutomation: protectedProcedure @@ -59,5 +60,6 @@ export const smartHomeRouter = createTRPCRouter({ const client = await createIntegrationAsync(integration); await client.triggerAutomationAsync(input.automationId); invalidateIntegrationDataCache(integration.id); + smartHomeEntityStateRequestHandler.invalidateCache(); }), }); diff --git a/packages/old-import/src/import/import-single-oldmarr.ts b/packages/old-import/src/import/import-single-oldmarr.ts index 2ca6ed4c1..9ce1d7bbf 100644 --- a/packages/old-import/src/import/import-single-oldmarr.ts +++ b/packages/old-import/src/import/import-single-oldmarr.ts @@ -39,4 +39,6 @@ export const importSingleOldmarrConfigAsync = async ( boardInsertCollection.insertAll(db); }, }); + + return boardInsertCollection.boards.map(({ id, name }) => ({ id, name })); }; diff --git a/packages/old-import/src/index.ts b/packages/old-import/src/index.ts index d3153a189..d66f4c83b 100644 --- a/packages/old-import/src/index.ts +++ b/packages/old-import/src/index.ts @@ -9,5 +9,5 @@ export const importOldmarrAsync = async ( old: OldmarrConfig, configuration: OldmarrImportConfiguration, ) => { - await importSingleOldmarrConfigAsync(db, old, configuration); + return await importSingleOldmarrConfigAsync(db, old, configuration); };