From 2704202ba94a6124300694dd9e8a8de7bb0e01d0 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 20 May 2026 12:08:20 -0700 Subject: [PATCH] Add button to rebuid cache --- server/private/routers/external.ts | 6 ++ server/routers/client/index.ts | 1 + .../rebuildClientAssociationsCacheRoute.ts | 81 +++++++++++++++++++ .../clients/user/[niceId]/general/page.tsx | 40 ++++++++- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 server/routers/client/rebuildClientAssociationsCacheRoute.ts diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index a48cc3813..007019756 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -782,3 +782,9 @@ authenticated.get( verifyClientAccess, client.verifyClientAssociationsCache ); + +authenticated.post( + "/client/:clientId/rebuild-associations-cache", + verifyClientAccess, + client.rebuildClientAssociationsCacheRoute +); diff --git a/server/routers/client/index.ts b/server/routers/client/index.ts index 145cdd306..2d3dd8c47 100644 --- a/server/routers/client/index.ts +++ b/server/routers/client/index.ts @@ -11,3 +11,4 @@ export * from "./updateClient"; export * from "./getClient"; export * from "./createUserClient"; export * from "./verifyClientAssociationsCache"; +export * from "./rebuildClientAssociationsCacheRoute"; diff --git a/server/routers/client/rebuildClientAssociationsCacheRoute.ts b/server/routers/client/rebuildClientAssociationsCacheRoute.ts new file mode 100644 index 000000000..32a6a407a --- /dev/null +++ b/server/routers/client/rebuildClientAssociationsCacheRoute.ts @@ -0,0 +1,81 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { clients } from "@server/db"; +import { eq } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; +import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; + +const paramsSchema = z.strictObject({ + clientId: z.string().transform(Number).pipe(z.int().positive()) +}); + +registry.registerPath({ + method: "post", + path: "/client/{clientId}/rebuild-associations-cache", + description: + "Rebuild the client's site/site-resource association cache based on current permissions.", + tags: [OpenAPITags.Client], + request: { + params: paramsSchema + }, + responses: {} +}); + +export async function rebuildClientAssociationsCacheRoute( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { clientId } = parsedParams.data; + + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Client with ID ${clientId} not found` + ) + ); + } + + await rebuildClientAssociationsFromClient(client); + + return response(res, { + data: null, + success: true, + error: false, + message: "Client association cache rebuilt successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to rebuild client association cache" + ) + ); + } +} diff --git a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx index af2d5fdad..bca04c8cb 100644 --- a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx @@ -165,6 +165,34 @@ export default function GeneralPage() { actualSiteIds: number[]; }>(null); const [isCheckingCache, setIsCheckingCache] = useState(false); + const [isRebuildingCache, setIsRebuildingCache] = useState(false); + + const handleRebuildCache = async () => { + if (!client.clientId) return; + setIsRebuildingCache(true); + try { + await api.post( + `/client/${client.clientId}/rebuild-associations-cache` + ); + // Re-verify after rebuild so the result refreshes + const res = await api.get( + `/client/${client.clientId}/verify-associations-cache` + ); + setCacheCheck(res.data.data); + toast({ + title: "Cache rebuilt", + description: "Association cache rebuilt successfully." + }); + } catch (e) { + toast({ + variant: "destructive", + title: "Rebuild failed", + description: formatAxiosError(e, "Failed to rebuild cache") + }); + } finally { + setIsRebuildingCache(false); + } + }; const handleVerifyCache = async () => { if (!client.clientId) return; @@ -904,7 +932,7 @@ export default function GeneralPage() { Cache is consistent ) : ( -
+
Cache is INCONSISTENT @@ -929,6 +957,16 @@ export default function GeneralPage() { Extra sites: [ {cacheCheck.extraSiteIds.join(", ")}]
+
)}