fix: address Bugbot review #3 — mutation invalidation gaps

- 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
This commit is contained in:
Thomas Camlong committed 2026-08-07 19:14:02 +02:00
1 parent 0f8362c3da
commit 4361c8dbe2
11 files changed
+31 -4

No files matched your search

+5 -1
View File
@@ -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({
+9 -1
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, 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")
@@ -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;
}),
});
+1
View File
@@ -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))
@@ -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) {
@@ -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();
}),
});
@@ -49,5 +49,6 @@ export const indexerManagerRouter = createTRPCRouter({
for (const integration of ctx.integrations) {
invalidateIntegrationDataCache(integration.id);
}
indexerManagerRequestHandler.invalidateCache();
}),
});
@@ -109,5 +109,7 @@ export const mediaRequestsRouter = createTRPCRouter({
await answerActions[input.answer](input.requestId);
invalidateIntegrationDataCache(integration.id);
mediaRequestListRequestHandler.invalidateCache();
mediaRequestStatsRequestHandler.invalidateCache();
}),
});
@@ -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();
}),
});
@@ -39,4 +39,6 @@ export const importSingleOldmarrConfigAsync = async (
boardInsertCollection.insertAll(db);
},
});
return boardInsertCollection.boards.map(({ id, name }) => ({ id, name }));
};
+1 -1
View File
@@ -9,5 +9,5 @@ export const importOldmarrAsync = async (
old: OldmarrConfig,
configuration: OldmarrImportConfiguration,
) => {
await importSingleOldmarrConfigAsync(db, old, configuration);
return await importSingleOldmarrConfigAsync(db, old, configuration);
};