From 3c3c859f12410033c3853a0eb140d4bf7bbacd96 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 17 Nov 2025 23:23:13 -0300 Subject: [PATCH 1/6] Group rotated secrets under rotation row on the UI dashboard --- .../src/server/routes/v1/dashboard-router.ts | 66 ++++++++++++++----- .../SecretDashboardPage.tsx | 23 +++++-- .../components/SecretListView/SecretItem.tsx | 10 ++- .../SecretRotationItem.tsx | 61 ++++++++++++++++- .../SecretRotationListView.tsx | 39 ++++++++++- 5 files changed, 172 insertions(+), 27 deletions(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 8cf9604a4..cf66cd898 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -624,7 +624,10 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { secretValueHidden: z.boolean(), secretPath: z.string().optional(), secretMetadata: ResourceMetadataSchema.optional(), - tags: SanitizedTagSchema.array().optional() + tags: SanitizedTagSchema.array().optional(), + reminder: RemindersSchema.extend({ + recipients: z.string().array() + }).nullable() }) .nullable() .array() @@ -743,6 +746,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { ReturnType >[number]["secrets"][number] & { isEmpty: boolean; + reminder: Awaited>[string] | null; } > | null)[]; })[] @@ -847,27 +851,38 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { ); if (remainingLimit > 0 && totalSecretRotationCount > adjustedOffset) { - secretRotations = ( - await server.services.secretRotationV2.getDashboardSecretRotations( - { - projectId, - search, - orderBy, - orderDirection, - environments: [environment], - secretPath, - limit: remainingLimit, - offset: adjustedOffset - }, - req.permission - ) - ).map((rotation) => ({ + const rawSecretRotations = await server.services.secretRotationV2.getDashboardSecretRotations( + { + projectId, + search, + orderBy, + orderDirection, + environments: [environment], + secretPath, + limit: remainingLimit, + offset: adjustedOffset + }, + req.permission + ); + + const allRotationSecretIds = rawSecretRotations + .flatMap((rotation) => rotation.secrets) + .filter((secret) => Boolean(secret)) + .map((secret) => secret.id); + + const rotationReminders = + allRotationSecretIds.length > 0 + ? await server.services.reminder.getRemindersForDashboard(allRotationSecretIds) + : {}; + + secretRotations = rawSecretRotations.map((rotation) => ({ ...rotation, secrets: rotation.secrets.map((secret) => secret ? { ...secret, - isEmpty: !secret.secretValue + isEmpty: secret.secretValueHidden, + reminder: rotationReminders[secret.id] ?? null } : secret ) @@ -978,11 +993,26 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { rawSecrets.map((secret) => secret.id) ); - secrets = rawSecrets.map((secret) => ({ + const rotationSecretIds = + includeSecretRotations && secretRotations?.length + ? new Set( + secretRotations.flatMap((rotation) => rotation.secrets.filter(Boolean).map((secret) => secret.id)) + ) + : new Set(); + + const filteredSecrets = rawSecrets.filter((secret) => !rotationSecretIds.has(secret.id)); + + secrets = filteredSecrets.map((secret) => ({ ...secret, isEmpty: !secret.secretValue, reminder: reminders[secret.id] ?? null })); + + if (includeSecretRotations && secretRotations?.length && totalSecretCount && rotationSecretIds.size > 0) { + const filteredCount = rawSecrets.filter((secret) => !rotationSecretIds.has(secret.id)).length; + const originalCount = rawSecrets.length; + totalSecretCount = Math.max(0, totalSecretCount - (originalCount - filteredCount)); + } } } } catch (error) { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index 203a3f6eb..441f69f7c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -656,12 +656,17 @@ const Page = () => { setDebouncedSearchFilter(""); }; - const getMergedSecretsWithPending = () => { + const getMergedSecretsWithPending = ( + paramSecrets?: (SecretV3RawSanitized | null)[] + ): SecretV3RawSanitized[] => { + const sanitizedParamSecrets = paramSecrets?.filter(Boolean) as + | SecretV3RawSanitized[] + | undefined; if (!isBatchMode || pendingChanges.secrets.length === 0) { - return secrets; + return sanitizedParamSecrets || secrets || []; } - const mergedSecrets = [...(secrets || [])] as (SecretV3RawSanitized & { + const mergedSecrets = [...(sanitizedParamSecrets || secrets || [])] as (SecretV3RawSanitized & { originalKey?: string; })[]; @@ -1041,7 +1046,17 @@ const Page = () => { /> )} {canReadSecretRotations && Boolean(secretRotations?.length) && ( - + )} {canReadSecret && Boolean(mergedSecrets?.length) && ( ( - + void; onViewGeneratedCredentials: () => void; onDelete: () => void; + projectId: string; + secretPath?: string; + tags?: WsTag[]; + isProtectedBranch?: boolean; + usedBySecretSyncs?: UsedBySecretSyncs[]; + importedBy?: { + environment: { name: string; slug: string }; + folders: { + name: string; + secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[]; + isImported: boolean; + }[]; + }[]; + colWidth: number; + getMergedSecretsWithPending: ( + paramSecrets?: (SecretV3RawSanitized | null)[] + ) => SecretV3RawSanitized[]; }; export const SecretRotationItem = ({ @@ -35,16 +55,40 @@ export const SecretRotationItem = ({ onEdit, onRotate, onViewGeneratedCredentials, - onDelete + onDelete, + projectId, + secretPath = "/", + tags = [], + isProtectedBranch = false, + usedBySecretSyncs, + importedBy, + colWidth, + getMergedSecretsWithPending }: Props) => { const { name, type, environment, folder, secrets, description } = secretRotation; const { name: rotationType, image } = SECRET_ROTATION_MAP[type]; const [showSecrets, setShowSecrets] = useState(false); + const [isExpanded, setIsExpanded] = useState(true); return ( <> -
+
setIsExpanded(!isExpanded)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsExpanded(!isExpanded); + } + }} + role="button" + tabIndex={0} + aria-expanded={isExpanded} + aria-label={`${isExpanded ? "Collapse" : "Expand"} rotation secrets for ${name}`} + >
@@ -198,6 +242,19 @@ export const SecretRotationItem = ({
+ {isExpanded && ( + + )} e.preventDefault()} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationListView.tsx index 1682a79df..1de6aa201 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationListView.tsx @@ -3,15 +3,44 @@ import { EditSecretRotationV2Modal } from "@app/components/secret-rotations-v2/E import { RotateSecretRotationV2Modal } from "@app/components/secret-rotations-v2/RotateSecretRotationV2Modal"; import { ViewSecretRotationV2GeneratedCredentialsModal } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials"; import { usePopUp } from "@app/hooks"; +import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types"; import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; +import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types"; import { SecretRotationItem } from "./SecretRotationItem"; type Props = { secretRotations?: TSecretRotationV2[]; + projectId: string; + secretPath?: string; + tags?: WsTag[]; + isProtectedBranch?: boolean; + usedBySecretSyncs?: UsedBySecretSyncs[]; + importedBy?: { + environment: { name: string; slug: string }; + folders: { + name: string; + secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[]; + isImported: boolean; + }[]; + }[]; + colWidth: number; + getMergedSecretsWithPending: ( + secretParams?: (SecretV3RawSanitized | null)[] + ) => SecretV3RawSanitized[]; }; -export const SecretRotationListView = ({ secretRotations }: Props) => { +export const SecretRotationListView = ({ + secretRotations, + projectId, + secretPath = "/", + tags = [], + isProtectedBranch = false, + usedBySecretSyncs, + importedBy, + colWidth, + getMergedSecretsWithPending +}: Props) => { const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ "editSecretRotation", "rotateSecretRotation", @@ -31,6 +60,14 @@ export const SecretRotationListView = ({ secretRotations }: Props) => { handlePopUpOpen("viewSecretRotationGeneratedCredentials", secretRotation) } onDelete={() => handlePopUpOpen("deleteSecretRotation", secretRotation)} + colWidth={colWidth} + tags={tags} + projectId={projectId} + secretPath={secretPath} + isProtectedBranch={isProtectedBranch} + importedBy={importedBy} + usedBySecretSyncs={usedBySecretSyncs} + getMergedSecretsWithPending={getMergedSecretsWithPending} /> ))} Date: Tue, 18 Nov 2025 02:07:02 -0300 Subject: [PATCH 2/6] Remove change made while testing --- backend/src/server/routes/v1/dashboard-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index cf66cd898..36331e437 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -881,7 +881,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { secret ? { ...secret, - isEmpty: secret.secretValueHidden, + isEmpty: secret.secretValue, reminder: rotationReminders[secret.id] ?? null } : secret From 1b346991427bcc18a9c2b24f6145e81289d9b077 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Tue, 18 Nov 2025 10:51:30 -0300 Subject: [PATCH 3/6] Lint fix --- backend/src/server/routes/v1/dashboard-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 36331e437..ca928a1ad 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -881,7 +881,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { secret ? { ...secret, - isEmpty: secret.secretValue, + isEmpty: !secret.secretValue, reminder: rotationReminders[secret.id] ?? null } : secret From 5cd6bf8213ab8a31dfd9cb8d9125a310e443ee03 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Wed, 19 Nov 2025 12:43:54 -0300 Subject: [PATCH 4/6] Fix table multiple pending changes --- .../secret-v2-bridge-service.ts | 21 +++++--- .../SecretListView/SecretListView.tsx | 51 +++++++++++-------- .../SecretRotationItem.tsx | 1 + 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 559c86843..1f27d1c7b 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -483,8 +483,8 @@ export const secretV2BridgeServiceFactory = ({ }); if (!sharedSecretToModify) throw new NotFoundError({ message: `Secret with name ${inputSecret.secretName} not found` }); - if (sharedSecretToModify.isRotatedSecret && (inputSecret.newSecretName || inputSecret.secretValue)) - throw new BadRequestError({ message: "Cannot update rotated secret name or value" }); + if (sharedSecretToModify.isRotatedSecret && inputSecret.newSecretName) + throw new BadRequestError({ message: "Cannot update rotated secret name" }); secretId = sharedSecretToModify.id; secret = sharedSecretToModify; } @@ -1934,8 +1934,14 @@ export const secretV2BridgeServiceFactory = ({ if (el.isRotatedSecret) { const input = secretsToUpdateGroupByPath[secretPath].find((i) => i.secretKey === el.key); - if (input && (input.newSecretName || input.secretValue)) - throw new BadRequestError({ message: `Cannot update rotated secret name or value: ${el.key}` }); + if (input) { + if (input.newSecretName) { + delete input.newSecretName; + } + if (input.secretValue !== undefined) { + delete input.secretValue; + } + } } }); @@ -2061,8 +2067,11 @@ export const secretV2BridgeServiceFactory = ({ commitChanges, inputSecrets: secretsToUpdate.map((el) => { const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0]; + const shouldUpdateValue = !originalSecret.isRotatedSecret && typeof el.secretValue !== "undefined"; + const shouldUpdateName = !originalSecret.isRotatedSecret && el.newSecretName; + const encryptedValue = - typeof el.secretValue !== "undefined" + shouldUpdateValue && el.secretValue !== undefined ? { encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob, references: secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences @@ -2077,7 +2086,7 @@ export const secretV2BridgeServiceFactory = ({ (value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob ), skipMultilineEncoding: el.skipMultilineEncoding, - key: el.newSecretName || el.secretKey, + key: shouldUpdateName ? el.newSecretName : el.secretKey, tags: el.tagIds, secretMetadata: el.secretMetadata, ...encryptedValue diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx index be5dfaaed..b6d358453 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx @@ -52,6 +52,7 @@ type Props = { }[]; }[]; colWidth: number; + excludePendingCreates?: boolean; }; export const SecretListView = ({ @@ -64,7 +65,8 @@ export const SecretListView = ({ isProtectedBranch = false, usedBySecretSyncs, importedBy, - colWidth + colWidth, + excludePendingCreates = false }: Props) => { const queryClient = useQueryClient(); const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([ @@ -580,27 +582,32 @@ export const SecretListView = ({ {FontAwesomeSpriteSymbols.map(({ icon, symbol }) => ( ))} - {secrets.map((secret) => ( - - ))} + {secrets + .filter((secret) => { + if (!excludePendingCreates) return true; + return !secret.isPending || secret.pendingAction !== PendingAction.Create; + }) + .map((secret) => ( + + ))} )} From ec5d740536e267ec48ffd589bfdc23b4b40bc595 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Tue, 9 Dec 2025 00:02:50 -0300 Subject: [PATCH 5/6] Fix secret rotation filter on dashboard query --- .../src/server/routes/v1/dashboard-router.ts | 23 ++++--------------- .../secret-v2-bridge/secret-v2-bridge-dal.ts | 10 ++++++++ .../secret-v2-bridge-service.ts | 1 + .../secret-v2-bridge-types.ts | 2 ++ backend/src/services/secret/secret-service.ts | 1 + backend/src/services/secret/secret-types.ts | 1 + 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index ca928a1ad..7dc763730 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -963,7 +963,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { search, tagSlugs: tags, includeTagsInSearch: true, - includeMetadataInSearch: true + includeMetadataInSearch: true, + excludeRotatedSecrets: includeSecretRotations }); if (remainingLimit > 0 && totalSecretCount > adjustedOffset) { @@ -985,7 +986,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { offset: adjustedOffset, tagSlugs: tags, includeTagsInSearch: true, - includeMetadataInSearch: true + includeMetadataInSearch: true, + excludeRotatedSecrets: includeSecretRotations }) ).secrets; @@ -993,26 +995,11 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { rawSecrets.map((secret) => secret.id) ); - const rotationSecretIds = - includeSecretRotations && secretRotations?.length - ? new Set( - secretRotations.flatMap((rotation) => rotation.secrets.filter(Boolean).map((secret) => secret.id)) - ) - : new Set(); - - const filteredSecrets = rawSecrets.filter((secret) => !rotationSecretIds.has(secret.id)); - - secrets = filteredSecrets.map((secret) => ({ + secrets = rawSecrets.map((secret) => ({ ...secret, isEmpty: !secret.secretValue, reminder: reminders[secret.id] ?? null })); - - if (includeSecretRotations && secretRotations?.length && totalSecretCount && rotationSecretIds.size > 0) { - const filteredCount = rawSecrets.filter((secret) => !rotationSecretIds.has(secret.id)).length; - const originalCount = rawSecrets.length; - totalSecretCount = Math.max(0, totalSecretCount - (originalCount - filteredCount)); - } } } } catch (error) { diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts index d8220e4f5..0753f9640 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts @@ -416,6 +416,7 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { tagSlugs?: string[]; includeTagsInSearch?: boolean; includeMetadataInSearch?: boolean; + excludeRotatedSecrets?: boolean; } ) => { try { @@ -481,6 +482,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { ); } + if (filters?.excludeRotatedSecrets) { + void query.whereNull(`${TableName.SecretRotationV2SecretMapping}.secretId`); + } + const secrets = await query; // @ts-expect-error not inferred by knex @@ -594,6 +599,11 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { void bd.whereIn(`${TableName.SecretTag}.slug`, slugs); } }) + .where((bd) => { + if (filters?.excludeRotatedSecrets) { + void bd.whereNull(`${TableName.SecretRotationV2SecretMapping}.secretId`); + } + }) .orderBy( filters?.orderBy === SecretsOrderBy.Name ? "key" : "id", filters?.orderDirection ?? OrderByDirection.ASC diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 09647f2d0..d8d06bd46 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -888,6 +888,7 @@ export const secretV2BridgeServiceFactory = ({ | "tagSlugs" | "environment" | "search" + | "excludeRotatedSecrets" >) => { const { permission } = await permissionService.getProjectPermission({ actor, diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts index 5e2ffc1a0..f8613f57a 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts @@ -50,6 +50,7 @@ export type TGetSecretsDTO = { limit?: number; search?: string; keys?: string[]; + excludeRotatedSecrets?: boolean; } & TProjectPermission; export type TGetSecretsMissingReadValuePermissionDTO = Omit< @@ -362,6 +363,7 @@ export type TFindSecretsByFolderIdsFilter = { includeTagsInSearch?: boolean; includeMetadataInSearch?: boolean; keys?: string[]; + excludeRotatedSecrets?: boolean; }; export type TGetSecretsRawByFolderMappingsDTO = { diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 9ba1df47d..27690249d 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -1154,6 +1154,7 @@ export const secretServiceFactory = ({ | "search" | "includeTagsInSearch" | "includeMetadataInSearch" + | "excludeRotatedSecrets" >) => { const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index d8c778d7e..be2c8b214 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -214,6 +214,7 @@ export type TGetSecretsRawDTO = { keys?: string[]; includeTagsInSearch?: boolean; includeMetadataInSearch?: boolean; + excludeRotatedSecrets?: boolean; } & TProjectPermission; export type TGetSecretAccessListDTO = { From f44dca7852d422923e09960d2e5d646eddd26874 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Tue, 9 Dec 2025 22:30:39 -0300 Subject: [PATCH 6/6] Fix dal dashboard query for secret rotations --- .../secret-rotation-v2-dal.ts | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts index cf236b56f..1718e09fd 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts @@ -214,7 +214,10 @@ export const secretRotationV2DALFactory = ( tx?: Knex ) => { try { - const extendedQuery = baseSecretRotationV2Query({ filter, db, tx, options }) + const { limit, offset = 0, sort, ...queryOptions } = options || {}; + const baseOptions = { ...queryOptions }; + + const subquery = baseSecretRotationV2Query({ filter, db, tx, options: baseOptions }) .join( TableName.SecretRotationV2SecretMapping, `${TableName.SecretRotationV2SecretMapping}.rotationId`, @@ -233,6 +236,7 @@ export const secretRotationV2DALFactory = ( ) .leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`) .select( + selectAllTableCols(TableName.SecretRotationV2), db.ref("id").withSchema(TableName.SecretV2).as("secretId"), db.ref("key").withSchema(TableName.SecretV2).as("secretKey"), db.ref("version").withSchema(TableName.SecretV2).as("secretVersion"), @@ -252,18 +256,31 @@ export const secretRotationV2DALFactory = ( db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"), db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"), - db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue") + db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue"), + db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.SecretRotationV2}."createdAt" DESC) as rank`) ); if (search) { - void extendedQuery.where((query) => { - void query + void subquery.where((qb) => { + void qb .whereILike(`${TableName.SecretV2}.key`, `%${search}%`) .orWhereILike(`${TableName.SecretRotationV2}.name`, `%${search}%`); }); } - const secretRotations = await extendedQuery; + let secretRotations: Awaited; + if (limit !== undefined) { + const rankOffset = offset + 1; + const queryWithLimit = (tx || db) + .with("inner", subquery) + .select("*") + .from("inner") + .where("inner.rank", ">=", rankOffset) + .andWhere("inner.rank", "<", rankOffset + limit); + secretRotations = (await queryWithLimit) as unknown as Awaited; + } else { + secretRotations = await subquery; + } if (!secretRotations.length) return [];