From b2d43eaf8fcf2efa66a28379a0eed937b432da38 Mon Sep 17 00:00:00 2001 From: prayansh_chhablani Date: Sun, 7 Sep 2025 00:28:13 +0530 Subject: [PATCH 1/6] feat: recursive nature added --- ...828110158_add_folderIds_to_secret_syncs.ts | 72 +++++++++++ backend/src/db/schemas/secret-sync-folders.ts | 21 ++++ .../secret-sync-endpoints.ts | 1 + .../secret-folder/secret-folder-dal.ts | 88 +++++++++++++- .../services/secret-sync/secret-sync-dal.ts | 113 ++++++++++++++---- .../services/secret-sync/secret-sync-queue.ts | 17 +-- .../secret-sync/secret-sync-schemas.ts | 7 +- .../secret-sync/secret-sync-service.ts | 29 +++-- .../services/secret-sync/secret-sync-types.ts | 1 + .../forms/CreateSecretSyncForm.tsx | 3 +- .../forms/SecretSyncSourceFields.tsx | 23 +++- .../forms/schemas/base-secret-sync-schema.ts | 1 + 12 files changed, 324 insertions(+), 52 deletions(-) create mode 100644 backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts create mode 100644 backend/src/db/schemas/secret-sync-folders.ts diff --git a/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts new file mode 100644 index 0000000000..f4b05799bf --- /dev/null +++ b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts @@ -0,0 +1,72 @@ +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; + +const TABLE = TableName.SecretSync; +const JOIN_TABLE = "secret_sync_folders"; + +export async function up(knex: Knex): Promise { + const hasFolderId = await knex.schema.hasColumn(TABLE, "folderId"); + + const hasJoinTable = await knex.schema.hasTable(JOIN_TABLE); + if (!hasJoinTable) { + await knex.schema.createTable(JOIN_TABLE, (t) => { + t.uuid("secretSyncId") + .notNullable() + .references("id") + .inTable(TABLE) + .onDelete("CASCADE"); + + t.uuid("folderId") + .notNullable() + .references("id") + .inTable("secret_folders") + .onDelete("CASCADE"); + + t.primary(["secretSyncId", "folderId"]); + t.timestamp("createdAt").defaultTo(knex.fn.now()); + t.timestamp("updatedAt").defaultTo(knex.fn.now()); + }); + } + + if (hasFolderId) { + await knex.raw(` + INSERT INTO "${JOIN_TABLE}" ("secretSyncId", "folderId", "createdAt", "updatedAt") + SELECT id, "folderId", NOW(), NOW() + FROM "${TABLE}" + WHERE "folderId" IS NOT NULL + `); + + await knex.schema.alterTable(TABLE, (t) => { + t.dropForeign(["folderId"]); + t.dropColumn("folderId"); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasCol = await knex.schema.hasColumn(TABLE, "folderId"); + + if (!hasCol) { + await knex.schema.alterTable(TABLE, (t) => { + t.uuid("folderId").nullable(); + t.foreign("folderId").references("id").inTable("secret_folders").onDelete("SET NULL"); + }); + + await knex.raw(` + UPDATE "${TABLE}" s + SET "folderId" = sub.folder_id + FROM ( + SELECT DISTINCT ON ("secretSyncId") "secretSyncId", "folderId" + FROM "${JOIN_TABLE}" + ORDER BY "secretSyncId", "createdAt" ASC + ) sub + WHERE s.id = sub."secretSyncId" + `); + } + + const hasJoinTable = await knex.schema.hasTable(JOIN_TABLE); + if (hasJoinTable) { + await knex.schema.dropTable(JOIN_TABLE); + } +} diff --git a/backend/src/db/schemas/secret-sync-folders.ts b/backend/src/db/schemas/secret-sync-folders.ts new file mode 100644 index 0000000000..d1c35ea876 --- /dev/null +++ b/backend/src/db/schemas/secret-sync-folders.ts @@ -0,0 +1,21 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + + + +import { TImmutableDBKeys } from "./models"; + +export const SecretSyncFoldersSchema = z.object({ + secretSyncId: z.string().uuid(), + folderId: z.string().uuid(), + createdAt: z.date().nullable().optional(), + updatedAt: z.date().nullable().optional() +}); + +export type TSecretSyncFolders = z.infer; +export type TSecretSyncFoldersInsert = Omit, TImmutableDBKeys>; +export type TSecretSyncFoldersUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts index 9db3a2013c..0dc58bfb26 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts @@ -29,6 +29,7 @@ export const registerSyncSecretsEndpoints = ; updateSchema: z.ZodType<{ connectionId?: string; diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 7dfeaddcf7..19edc37fa4 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -88,6 +88,85 @@ const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: str .from("parent"); }; +const sqlListDescendantsFromFolder = ( + db: Knex, + folder: { + id: string; + path: string; + projectId: string; + envSlug: string; + envName: string; + envId: string; + name: string; + version?: number | null; + createdAt: Date; + updatedAt: Date; + parentId?: string | null; + isReserved?: boolean | null; + description?: string | null; + lastSecretModified?: Date | null; + }) => { + const parentId = folder.parentId ?? null; + const description = folder.description ?? null; + + return db + .with("seed", (qb) => + void qb + .select({ + depth: db.raw("1"), + path: db.raw("?::text", [folder.path]), + envSlug: db.raw("?::text", [folder.envSlug]), + envName: db.raw("?::text", [folder.envName]), + projectId: db.raw("?::uuid", [folder.projectId]), + + id: db.raw("?::uuid", [folder.id]), + name: db.raw("?::text", [folder.name]), + version: db.raw("?::int", [folder.version]), + createdAt: db.raw("?::timestamptz", [folder.createdAt]), + updatedAt: db.raw("?::timestamptz", [folder.updatedAt]), + parentId: db.raw("?::uuid", [parentId]), + isReserved: db.raw("?::boolean", [folder.isReserved]), + description: db.raw("?::text", [description]), + lastSecretModified: db.raw("?::timestamptz", [folder.lastSecretModified]), + envId: db.raw("?::uuid", [folder.envId]) + }) + ) + + .withRecursive("tree", (baseQb) => { + void baseQb + .select("*") + .from("seed") + .union((qb) => + void qb + .select({ + depth: db.raw("tree.depth + 1"), + path: db.raw( + "CONCAT((CASE WHEN tree.path = '/' THEN '' ELSE tree.path END),'/', sf.name)" + ), + envSlug: db.ref("envSlug").withSchema("tree"), + envName: db.ref("envName").withSchema("tree"), + projectId: db.ref("projectId").withSchema("tree"), + + id: db.ref("id").withSchema("sf"), + name: db.ref("name").withSchema("sf"), + version: db.ref("version").withSchema("sf"), + createdAt: db.ref("createdAt").withSchema("sf"), + updatedAt: db.ref("updatedAt").withSchema("sf"), + parentId: db.ref("parentId").withSchema("sf"), + isReserved: db.ref("isReserved").withSchema("sf"), + description: db.ref("description").withSchema("sf"), + lastSecretModified: db.ref("lastSecretModified").withSchema("sf"), + envId: db.ref("envId").withSchema("sf") + }) + .from({ sf: TableName.SecretFolder }) + .join("tree" , "tree.id", "sf.parentId") + ); + }) + .from("tree") + .select("*") + .orderBy([{ column: "depth" }, { column: "path" }]); + } + const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: string[], secretPath: string) => { // this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2 const formatedPath = secretPath.at(-1) === "/" && secretPath.length > 1 ? secretPath.slice(0, -1) : secretPath; @@ -211,7 +290,7 @@ export const ROOT_FOLDER_NAME = "root"; export const secretFolderDALFactory = (db: TDbClient) => { const secretFolderOrm = ormify(db, TableName.SecretFolder); - const findBySecretPath = async (projectId: string, environment: string, path: string, tx?: Knex) => { + const findBySecretPath = async (projectId: string, environment: string, path: string, tx?: Knex, recursive: boolean = false) => { const isValidPath = isValidSecretPath(path); if (!isValidPath) throw new BadRequestError({ @@ -225,12 +304,15 @@ export const secretFolderDALFactory = (db: TDbClient) => { const folder = await query; if (!folder) return; const { envId: id, envName: name, envSlug: slug, ...el } = folder; - return { ...el, envId: id, environment: { id, name, slug } }; + if(!recursive) + { + return { ...el, envId: id, environment: { id, name, slug } }; + } + return await sqlListDescendantsFromFolder(tx || db.replicaNode(), folder); } catch (error) { throw new DatabaseError({ error, name: "Find by secret path" }); } }; - // finds folders by path for multiple envs const findBySecretPathMultiEnv = async (projectId: string, environments: string[], path: string, tx?: Knex) => { const isValidPath = isValidSecretPath(path); diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index e50593f100..1a897bca97 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -13,12 +13,20 @@ type SecretSyncFindFilter = Parameters>[0]; const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: SecretSyncFindFilter; tx?: Knex }) => { const query = (tx || db.replicaNode())(TableName.SecretSync) - .leftJoin(TableName.SecretFolder, `${TableName.SecretSync}.folderId`, `${TableName.SecretFolder}.id`) + .leftJoin(TableName.SecretSyncFolders, `${TableName.SecretSync}.id`, `${TableName.SecretSyncFolders}.secretSyncId`) + .leftJoin(TableName.SecretFolder, `${TableName.SecretSyncFolders}.folderId`, `${TableName.SecretFolder}.id`) .leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .join(TableName.AppConnection, `${TableName.SecretSync}.connectionId`, `${TableName.AppConnection}.id`) .select(selectAllTableCols(TableName.SecretSync)) .select( // environment + db.raw( + `coalesce(array_agg(distinct ??) filter (where ?? is not null), '{}') as "folderId"`, + [ + `${TableName.SecretSyncFolders}.folderId`, + `${TableName.SecretSyncFolders}.folderId` + ] + ), db.ref("name").withSchema(TableName.Environment).as("envName"), db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"), @@ -37,11 +45,34 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre .ref("isPlatformManagedCredentials") .withSchema(TableName.AppConnection) .as("connectionIsPlatformManagedCredentials") + ) + .groupBy( + `${TableName.SecretSync}.id`, + `${TableName.Environment}.id`, + `${TableName.AppConnection}.id` ); if (filter) { /* eslint-disable @typescript-eslint/no-misused-promises */ - void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.SecretSync, filter))); + const { $in, folderId, ...rest } = filter; + + if ($in && $in.folderId) { + void query.whereIn( + `${TableName.SecretSyncFolders}.folderId`, + $in.folderId + ); + } + + if (folderId) { + void query.where( + `${TableName.SecretSyncFolders}.folderId`, + folderId + ); + } + + if(Object.keys(rest).length > 0) { + void query.where(prependTableNameToFindFilter(TableName.SecretSync, rest)); + } } return query; @@ -49,7 +80,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre const expandSecretSync = ( secretSync: Awaited>[number], - folder?: Awaited>[number] + folder?: Awaited> ) => { const { envId, @@ -88,12 +119,14 @@ const expandSecretSync = ( isPlatformManagedCredentials: connectionIsPlatformManagedCredentials, gatewayId: connectionGatewayId }, - folder: folder - ? { - id: folder.id, - path: folder.path - } - : null + folder: folder && folder.length > 0 + ? folder + .filter((f): f is NonNullable => f !== undefined) + .map(f => ({ + id: f.id, + path: f.path + })) + : [] }; }; @@ -102,6 +135,7 @@ export const secretSyncDALFactory = ( folderDAL: Pick ) => { const secretSyncOrm = ormify(db, TableName.SecretSync); + const secretSyncOrmWithFolder = ormify(db, TableName.SecretSyncFolders) const findById = async (id: string, tx?: Knex) => { try { @@ -113,8 +147,8 @@ export const secretSyncDALFactory = ( if (secretSync) { // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId - ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) + const folderWithPath = secretSync.folderId + ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, secretSync.folderId) : []; return expandSecretSync(secretSync, folderWithPath); } @@ -123,7 +157,7 @@ export const secretSyncDALFactory = ( } }; - const create = async (data: Parameters<(typeof secretSyncOrm)["create"]>[0]) => { + const create = async (data: Parameters<(typeof secretSyncOrm)["create"]>[0], folderIds?: Parameters<(typeof secretSyncOrmWithFolder)["insertMany"]>[0]) => { const secretSync = (await secretSyncOrm.transaction(async (tx) => { const sync = await secretSyncOrm.create(data, tx); @@ -134,9 +168,25 @@ export const secretSyncDALFactory = ( }).first(); }))!; + const secretSyncId = secretSync.id; + + await secretSyncOrmWithFolder.transaction(async (tx) => { + if (folderIds && folderIds.length > 0) { + const folderData = folderIds.map((folderId: string) => ({ + folderId, + secretSyncId + })); + await secretSyncOrmWithFolder.insertMany(folderData, tx); + } + }); + + const normalizedFolderIds = Array.isArray(folderIds) + ? folderIds + : [folderIds]; + // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId - ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) + const folderWithPath = normalizedFolderIds.length + ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, normalizedFolderIds) : []; return expandSecretSync(secretSync, folderWithPath); }; @@ -153,19 +203,26 @@ export const secretSyncDALFactory = ( }))!; // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId - ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) + const folderWithPath = secretSync.folderId + ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, secretSync.folderId) : []; return expandSecretSync(secretSync, folderWithPath); }; + const deleteById = async (syncId: string) => { + return secretSyncOrm.transaction(async (tx) => { + await secretSyncOrmWithFolder.delete({ secretSyncId: syncId }, tx); + return secretSyncOrm.deleteById(syncId, tx); + }); + } + const findOne = async (filter: Parameters<(typeof secretSyncOrm)["findOne"]>[0], tx?: Knex) => { try { const secretSync = await baseSecretSyncQuery({ filter, db, tx }).first(); if (secretSync) { // TODO (scott): replace with cached folder path once implemented - const [folderWithPath] = secretSync.folderId + const folderWithPath = secretSync.folderId ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, [secretSync.folderId]) : []; return expandSecretSync(secretSync, folderWithPath); @@ -181,9 +238,13 @@ export const secretSyncDALFactory = ( if (!secretSyncs.length) return []; + const folderIds = secretSyncs + .filter((sync) => Array.isArray(sync.folderId) && sync.folderId.length > 0) + .flatMap((sync) => sync.folderId); + const foldersWithPath = await folderDAL.findSecretPathByFolderIds( secretSyncs[0].projectId, - secretSyncs.filter((sync) => Boolean(sync.folderId)).map((sync) => sync.folderId!) + folderIds ); // TODO (scott): replace with cached folder path once implemented @@ -193,13 +254,21 @@ export const secretSyncDALFactory = ( if (folder) folderRecord[folder.id] = folder; }); - return secretSyncs.map((secretSync) => - expandSecretSync(secretSync, secretSync.folderId ? folderRecord[secretSync.folderId] : undefined) - ); + return secretSyncs.map((secretSync) => { + return expandSecretSync( + secretSync, + secretSync.folderId + ? Array.isArray(secretSync.folderId) + ? secretSync.folderId.map((fid: string) => folderRecord[fid]) + : folderRecord[secretSync.folderId as string] + : undefined + ) + }); + } catch (error) { throw new DatabaseError({ error, name: "Find - Secret Sync" }); } }; - return { ...secretSyncOrm, findById, findOne, find, create, updateById }; + return { ...secretSyncOrm, ...secretSyncOrmWithFolder, deleteById, findById, findOne, find, create, updateById }; }; diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index 7bef7d8c78..a54bc21b74 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -72,6 +72,7 @@ type TSecretSyncQueueFactoryDep = { secretV2BridgeDAL: Pick< TSecretV2BridgeDALFactory, | "findByFolderId" + | "findByFolderIds" | "find" | "insertMany" | "upsertSecretReferences" @@ -224,7 +225,7 @@ export const secretSyncQueueFactory = ({ canExpandValue: () => true }); - const secrets = await secretV2BridgeDAL.findByFolderId({ folderId }); + const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds : folderId }); await Promise.allSettled( secrets.map(async (secret) => { @@ -251,7 +252,7 @@ export const secretSyncQueueFactory = ({ if (!includeImports) return secretMap; - const secretImports = await secretImportDAL.find({ folderId, isReplication: false }); + const secretImports = await secretImportDAL.findByFolderIds(folderId); if (secretImports.length) { const importedSecrets = await fnSecretsV2FromImports({ @@ -437,7 +438,7 @@ export const secretSyncQueueFactory = ({ }); logger.info( - `SecretSync Sync [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Sync [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); let isSynced = false; @@ -493,7 +494,7 @@ export const secretSyncQueueFactory = ({ } catch (err) { logger.error( err, - `SecretSync Sync Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Sync Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { @@ -580,7 +581,7 @@ export const secretSyncQueueFactory = ({ }); logger.info( - `SecretSync Import [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Import [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); let isSuccess = false; @@ -613,7 +614,7 @@ export const secretSyncQueueFactory = ({ } catch (err) { logger.error( err, - `SecretSync Import Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Import Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { @@ -704,7 +705,7 @@ export const secretSyncQueueFactory = ({ }); logger.info( - `SecretSync Remove [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Remove [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); let isSuccess = false; @@ -744,7 +745,7 @@ export const secretSyncQueueFactory = ({ } catch (err) { logger.error( err, - `SecretSync Remove Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [folderId=${secretSync.folderId}] [connectionId=${secretSync.connectionId}]` + `SecretSync Remove Error [syncId=${secretSync.id}] [destination=${secretSync.destination}] [projectId=${secretSync.projectId}] [connectionId=${secretSync.connectionId}]` ); if (appCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) { diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts index 3622ef3d04..b78ed264d2 100644 --- a/backend/src/services/secret-sync/secret-sync-schemas.ts +++ b/backend/src/services/secret-sync/secret-sync-schemas.ts @@ -85,8 +85,8 @@ export const BaseSecretSyncSchema = ( destination: SecretSync, @@ -111,7 +111,8 @@ export const GenericCreateSecretSyncFieldsSchema = ( diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 3fdb7fea67..a5434ee911 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -95,7 +95,7 @@ export const secretSyncServiceFactory = ({ secretSync.environment && secretSync.folder ? subject(ProjectPermissionSub.SecretSyncs, { environment: secretSync.environment.slug, - secretPath: secretSync.folder.path + secretPath: secretSync.folder.map((f: { path: string }) => f.path) }) : ProjectPermissionSub.SecretSyncs ) @@ -219,7 +219,7 @@ export const secretSyncServiceFactory = ({ }; const createSecretSync = async ( - { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, + { projectId, secretPath, environment, recursive, ...params }: TCreateSecretSyncDTO, actor: OrgServiceActor ) => { await enterpriseSyncCheck( @@ -257,9 +257,19 @@ export const secretSyncServiceFactory = ({ } ); - const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); + const foldersRaw = await folderDAL.findBySecretPath(projectId, environment, secretPath, undefined, recursive); + const result = { + commonData: { + ...params, + ...(params.isAutoSyncEnabled && { syncStatus: SecretSyncStatus.Pending }), + projectId + }, + folderIds: (Array.isArray(foldersRaw) ? foldersRaw : [foldersRaw]) + .filter(Boolean) + .map((folder: any) => folder.id) + }; - if (!folder) + if (!result) throw new BadRequestError({ message: `Could not find folder with path "${secretPath}" in environment "${environment}" for project with ID "${projectId}"` }); @@ -270,20 +280,15 @@ export const secretSyncServiceFactory = ({ await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); try { - const secretSync = await secretSyncDAL.create({ - folderId: folder.id, - ...params, - ...(params.isAutoSyncEnabled && { syncStatus: SecretSyncStatus.Pending }), - projectId - }); + const secretSync = await secretSyncDAL.create(result.commonData, result.folderIds); if (secretSync.isAutoSyncEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); - return secretSync as TSecretSync; + return secretSync as TSecretSync; } catch (err) { if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { throw new BadRequestError({ - message: `A Secret Sync with the name "${params.name}" already exists for the project with ID "${folder.projectId}"` + message: `A Secret Sync with the name "${params.name}" already exists for the project with ID "${foldersRaw[0].projectId}"` }); } diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index 6435e19d39..4c8bed4b8f 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -311,6 +311,7 @@ export type TCreateSecretSyncDTO = Pick> & { diff --git a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index 8a1be69c49..9b5334bdcd 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -32,7 +32,7 @@ type Props = { }; const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[] }[] = [ - { name: "Source", key: "source", fields: ["secretPath", "environment"] }, + { name: "Source", key: "source", fields: ["secretPath", "environment", "recursive"] }, { name: "Destination", key: "destination", fields: ["connection", "destinationConfig"] }, { name: "Sync Options", key: "options", fields: ["syncOptions"] }, { name: "Details", key: "details", fields: ["name", "description"] }, @@ -66,6 +66,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop const onSubmit = async ({ environment, connection, ...formData }: TSecretSyncForm) => { try { + console.log("I am formdata: ", formData) const secretSync = await createSecretSync.mutateAsync({ ...formData, connectionId: connection.id, diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx index 850ebbc801..72caf30edf 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { subject } from "@casl/ability"; -import { FilterableSelect, FormControl } from "@app/components/v2"; +import { Checkbox, FilterableSelect, FormControl } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useProjectPermission, useWorkspace } from "@app/context"; import { @@ -20,6 +20,7 @@ export const SecretSyncSourceFields = () => { const selectedEnvironment = watch("environment"); const selectedSecretPath = watch("secretPath"); + const recursive = watch("recursive"); useEffect(() => { const hasAccessToSource = @@ -28,7 +29,8 @@ export const SecretSyncSourceFields = () => { ProjectPermissionSecretSyncActions.Create, subject(ProjectPermissionSub.SecretSyncs, { environment: selectedEnvironment.slug, - secretPath: selectedSecretPath + secretPath: selectedSecretPath, + recursive: recursive }) ); @@ -39,7 +41,7 @@ export const SecretSyncSourceFields = () => { } else { clearErrors("secretPath"); } - }, [selectedEnvironment, selectedSecretPath]); + }, [selectedEnvironment, selectedSecretPath, recursive]); return ( <> @@ -78,6 +80,21 @@ export const SecretSyncSourceFields = () => { control={control} name="secretPath" /> + ( +
+ Recursive + +
+ )} + /> ); }; diff --git a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts index 1d925eb076..c50ef823a3 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts @@ -50,6 +50,7 @@ export const BaseSecretSyncSchema = Date: Sun, 7 Sep 2025 10:30:03 +0530 Subject: [PATCH 2/6] linting --- ...828110158_add_folderIds_to_secret_syncs.ts | 12 +-- backend/src/db/schemas/secret-sync-folders.ts | 2 - .../secret-folder/secret-folder-dal.ts | 86 ++++++++++--------- .../services/secret-sync/secret-sync-dal.ts | 75 +++++++--------- .../services/secret-sync/secret-sync-queue.ts | 2 +- .../secret-sync/secret-sync-schemas.ts | 2 +- .../secret-sync/secret-sync-service.ts | 8 +- 7 files changed, 83 insertions(+), 104 deletions(-) diff --git a/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts index f4b05799bf..c5d541cfde 100644 --- a/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts +++ b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts @@ -11,17 +11,9 @@ export async function up(knex: Knex): Promise { const hasJoinTable = await knex.schema.hasTable(JOIN_TABLE); if (!hasJoinTable) { await knex.schema.createTable(JOIN_TABLE, (t) => { - t.uuid("secretSyncId") - .notNullable() - .references("id") - .inTable(TABLE) - .onDelete("CASCADE"); + t.uuid("secretSyncId").notNullable().references("id").inTable(TABLE).onDelete("CASCADE"); - t.uuid("folderId") - .notNullable() - .references("id") - .inTable("secret_folders") - .onDelete("CASCADE"); + t.uuid("folderId").notNullable().references("id").inTable("secret_folders").onDelete("CASCADE"); t.primary(["secretSyncId", "folderId"]); t.timestamp("createdAt").defaultTo(knex.fn.now()); diff --git a/backend/src/db/schemas/secret-sync-folders.ts b/backend/src/db/schemas/secret-sync-folders.ts index d1c35ea876..f521193e73 100644 --- a/backend/src/db/schemas/secret-sync-folders.ts +++ b/backend/src/db/schemas/secret-sync-folders.ts @@ -5,8 +5,6 @@ import { z } from "zod"; - - import { TImmutableDBKeys } from "./models"; export const SecretSyncFoldersSchema = z.object({ diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 19edc37fa4..6d306bbf99 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -105,14 +105,16 @@ const sqlListDescendantsFromFolder = ( isReserved?: boolean | null; description?: string | null; lastSecretModified?: Date | null; - }) => { - const parentId = folder.parentId ?? null; - const description = folder.description ?? null; + } +) => { + const parentId = folder.parentId ?? null; + const description = folder.description ?? null; - return db - .with("seed", (qb) => - void qb - .select({ + return db + .with( + "seed", + (qb) => + void qb.select({ depth: db.raw("1"), path: db.raw("?::text", [folder.path]), envSlug: db.raw("?::text", [folder.envSlug]), @@ -130,42 +132,41 @@ const sqlListDescendantsFromFolder = ( lastSecretModified: db.raw("?::timestamptz", [folder.lastSecretModified]), envId: db.raw("?::uuid", [folder.envId]) }) - ) + ) - .withRecursive("tree", (baseQb) => { - void baseQb + .withRecursive("tree", (baseQb) => { + void baseQb .select("*") .from("seed") - .union((qb) => - void qb - .select({ - depth: db.raw("tree.depth + 1"), - path: db.raw( - "CONCAT((CASE WHEN tree.path = '/' THEN '' ELSE tree.path END),'/', sf.name)" - ), - envSlug: db.ref("envSlug").withSchema("tree"), - envName: db.ref("envName").withSchema("tree"), - projectId: db.ref("projectId").withSchema("tree"), - - id: db.ref("id").withSchema("sf"), - name: db.ref("name").withSchema("sf"), - version: db.ref("version").withSchema("sf"), - createdAt: db.ref("createdAt").withSchema("sf"), - updatedAt: db.ref("updatedAt").withSchema("sf"), - parentId: db.ref("parentId").withSchema("sf"), - isReserved: db.ref("isReserved").withSchema("sf"), - description: db.ref("description").withSchema("sf"), - lastSecretModified: db.ref("lastSecretModified").withSchema("sf"), - envId: db.ref("envId").withSchema("sf") - }) - .from({ sf: TableName.SecretFolder }) - .join("tree" , "tree.id", "sf.parentId") - ); - }) + .union( + (qb) => + void qb + .select({ + depth: db.raw("tree.depth + 1"), + path: db.raw("CONCAT((CASE WHEN tree.path = '/' THEN '' ELSE tree.path END),'/', sf.name)"), + envSlug: db.ref("envSlug").withSchema("tree"), + envName: db.ref("envName").withSchema("tree"), + projectId: db.ref("projectId").withSchema("tree"), + + id: db.ref("id").withSchema("sf"), + name: db.ref("name").withSchema("sf"), + version: db.ref("version").withSchema("sf"), + createdAt: db.ref("createdAt").withSchema("sf"), + updatedAt: db.ref("updatedAt").withSchema("sf"), + parentId: db.ref("parentId").withSchema("sf"), + isReserved: db.ref("isReserved").withSchema("sf"), + description: db.ref("description").withSchema("sf"), + lastSecretModified: db.ref("lastSecretModified").withSchema("sf"), + envId: db.ref("envId").withSchema("sf") + }) + .from({ sf: TableName.SecretFolder }) + .join("tree", "tree.id", "sf.parentId") + ); + }) .from("tree") .select("*") .orderBy([{ column: "depth" }, { column: "path" }]); - } +}; const sqlFindFolderByPathQuery = (db: Knex, projectId: string, environments: string[], secretPath: string) => { // this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2 @@ -290,7 +291,13 @@ export const ROOT_FOLDER_NAME = "root"; export const secretFolderDALFactory = (db: TDbClient) => { const secretFolderOrm = ormify(db, TableName.SecretFolder); - const findBySecretPath = async (projectId: string, environment: string, path: string, tx?: Knex, recursive: boolean = false) => { + const findBySecretPath = async ( + projectId: string, + environment: string, + path: string, + tx?: Knex, + recursive: boolean = false + ) => { const isValidPath = isValidSecretPath(path); if (!isValidPath) throw new BadRequestError({ @@ -304,8 +311,7 @@ export const secretFolderDALFactory = (db: TDbClient) => { const folder = await query; if (!folder) return; const { envId: id, envName: name, envSlug: slug, ...el } = folder; - if(!recursive) - { + if (!recursive) { return { ...el, envId: id, environment: { id, name, slug } }; } return await sqlListDescendantsFromFolder(tx || db.replicaNode(), folder); diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index 1a897bca97..9ddcfe0e4f 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -20,13 +20,10 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre .select(selectAllTableCols(TableName.SecretSync)) .select( // environment - db.raw( - `coalesce(array_agg(distinct ??) filter (where ?? is not null), '{}') as "folderId"`, - [ - `${TableName.SecretSyncFolders}.folderId`, - `${TableName.SecretSyncFolders}.folderId` - ] - ), + db.raw(`coalesce(array_agg(distinct ??) filter (where ?? is not null), '{}') as "folderId"`, [ + `${TableName.SecretSyncFolders}.folderId`, + `${TableName.SecretSyncFolders}.folderId` + ]), db.ref("name").withSchema(TableName.Environment).as("envName"), db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"), @@ -46,31 +43,21 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre .withSchema(TableName.AppConnection) .as("connectionIsPlatformManagedCredentials") ) - .groupBy( - `${TableName.SecretSync}.id`, - `${TableName.Environment}.id`, - `${TableName.AppConnection}.id` - ); + .groupBy(`${TableName.SecretSync}.id`, `${TableName.Environment}.id`, `${TableName.AppConnection}.id`); if (filter) { /* eslint-disable @typescript-eslint/no-misused-promises */ const { $in, folderId, ...rest } = filter; - + if ($in && $in.folderId) { - void query.whereIn( - `${TableName.SecretSyncFolders}.folderId`, - $in.folderId - ); + void query.whereIn(`${TableName.SecretSyncFolders}.folderId`, $in.folderId); } if (folderId) { - void query.where( - `${TableName.SecretSyncFolders}.folderId`, - folderId - ); + void query.where(`${TableName.SecretSyncFolders}.folderId`, folderId); } - if(Object.keys(rest).length > 0) { + if (Object.keys(rest).length > 0) { void query.where(prependTableNameToFindFilter(TableName.SecretSync, rest)); } } @@ -119,14 +106,15 @@ const expandSecretSync = ( isPlatformManagedCredentials: connectionIsPlatformManagedCredentials, gatewayId: connectionGatewayId }, - folder: folder && folder.length > 0 - ? folder - .filter((f): f is NonNullable => f !== undefined) - .map(f => ({ - id: f.id, - path: f.path - })) - : [] + folder: + folder && folder.length > 0 + ? folder + .filter((f): f is NonNullable => f !== undefined) + .map((f) => ({ + id: f.id, + path: f.path + })) + : [] }; }; @@ -135,7 +123,7 @@ export const secretSyncDALFactory = ( folderDAL: Pick ) => { const secretSyncOrm = ormify(db, TableName.SecretSync); - const secretSyncOrmWithFolder = ormify(db, TableName.SecretSyncFolders) + const secretSyncOrmWithFolder = ormify(db, TableName.SecretSyncFolders); const findById = async (id: string, tx?: Knex) => { try { @@ -157,7 +145,10 @@ export const secretSyncDALFactory = ( } }; - const create = async (data: Parameters<(typeof secretSyncOrm)["create"]>[0], folderIds?: Parameters<(typeof secretSyncOrmWithFolder)["insertMany"]>[0]) => { + const create = async ( + data: Parameters<(typeof secretSyncOrm)["create"]>[0], + folderIds?: Parameters<(typeof secretSyncOrmWithFolder)["insertMany"]>[0] + ) => { const secretSync = (await secretSyncOrm.transaction(async (tx) => { const sync = await secretSyncOrm.create(data, tx); @@ -175,14 +166,12 @@ export const secretSyncDALFactory = ( const folderData = folderIds.map((folderId: string) => ({ folderId, secretSyncId - })); + })); await secretSyncOrmWithFolder.insertMany(folderData, tx); } }); - const normalizedFolderIds = Array.isArray(folderIds) - ? folderIds - : [folderIds]; + const normalizedFolderIds = Array.isArray(folderIds) ? folderIds : [folderIds]; // TODO (scott): replace with cached folder path once implemented const folderWithPath = normalizedFolderIds.length @@ -214,7 +203,7 @@ export const secretSyncDALFactory = ( await secretSyncOrmWithFolder.delete({ secretSyncId: syncId }, tx); return secretSyncOrm.deleteById(syncId, tx); }); - } + }; const findOne = async (filter: Parameters<(typeof secretSyncOrm)["findOne"]>[0], tx?: Knex) => { try { @@ -239,13 +228,10 @@ export const secretSyncDALFactory = ( if (!secretSyncs.length) return []; const folderIds = secretSyncs - .filter((sync) => Array.isArray(sync.folderId) && sync.folderId.length > 0) - .flatMap((sync) => sync.folderId); + .filter((sync) => Array.isArray(sync.folderId) && sync.folderId.length > 0) + .flatMap((sync) => sync.folderId); - const foldersWithPath = await folderDAL.findSecretPathByFolderIds( - secretSyncs[0].projectId, - folderIds - ); + const foldersWithPath = await folderDAL.findSecretPathByFolderIds(secretSyncs[0].projectId, folderIds); // TODO (scott): replace with cached folder path once implemented const folderRecord: Record = {}; @@ -262,9 +248,8 @@ export const secretSyncDALFactory = ( ? secretSync.folderId.map((fid: string) => folderRecord[fid]) : folderRecord[secretSync.folderId as string] : undefined - ) + ); }); - } catch (error) { throw new DatabaseError({ error, name: "Find - Secret Sync" }); } diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index a54bc21b74..c1354169f5 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -225,7 +225,7 @@ export const secretSyncQueueFactory = ({ canExpandValue: () => true }); - const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds : folderId }); + const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds: folderId }); await Promise.allSettled( secrets.map(async (secret) => { diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts index b78ed264d2..dbfe463b25 100644 --- a/backend/src/services/secret-sync/secret-sync-schemas.ts +++ b/backend/src/services/secret-sync/secret-sync-schemas.ts @@ -86,7 +86,7 @@ export const BaseSecretSyncSchema = ( destination: SecretSync, diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index a5434ee911..ee8b181ee4 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -264,9 +264,7 @@ export const secretSyncServiceFactory = ({ ...(params.isAutoSyncEnabled && { syncStatus: SecretSyncStatus.Pending }), projectId }, - folderIds: (Array.isArray(foldersRaw) ? foldersRaw : [foldersRaw]) - .filter(Boolean) - .map((folder: any) => folder.id) + folderIds: (Array.isArray(foldersRaw) ? foldersRaw : [foldersRaw]).filter(Boolean).map((folder: any) => folder.id) }; if (!result) @@ -280,11 +278,11 @@ export const secretSyncServiceFactory = ({ await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); try { - const secretSync = await secretSyncDAL.create(result.commonData, result.folderIds); + const secretSync = await secretSyncDAL.create(result.commonData, result.folderIds); if (secretSync.isAutoSyncEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id }); - return secretSync as TSecretSync; + return secretSync as TSecretSync; } catch (err) { if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { throw new BadRequestError({ From 0851a1f408986ce625a6c11c98155f2f98702ef2 Mon Sep 17 00:00:00 2001 From: prayansh_chhablani Date: Sun, 7 Sep 2025 10:33:01 +0530 Subject: [PATCH 3/6] remove console.log --- .../src/components/secret-syncs/forms/CreateSecretSyncForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index 9b5334bdcd..712af1d34f 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -66,7 +66,6 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop const onSubmit = async ({ environment, connection, ...formData }: TSecretSyncForm) => { try { - console.log("I am formdata: ", formData) const secretSync = await createSecretSync.mutateAsync({ ...formData, connectionId: connection.id, From bf6d3bfdf9cd76c8e08c5a1aed649e5a2c8483aa Mon Sep 17 00:00:00 2001 From: prayansh_chhablani Date: Sun, 7 Sep 2025 16:45:30 +0530 Subject: [PATCH 4/6] address greptile comments --- ...828110158_add_folderIds_to_secret_syncs.ts | 2 +- .../services/secret-sync/secret-sync-dal.ts | 25 +++++++------------ .../secret-sync/secret-sync-schemas.ts | 11 +++++--- .../secret-sync/secret-sync-service.ts | 6 +++-- .../forms/SecretSyncSourceFields.tsx | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts index c5d541cfde..a129858e56 100644 --- a/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts +++ b/backend/src/db/migrations/20250828110158_add_folderIds_to_secret_syncs.ts @@ -47,7 +47,7 @@ export async function down(knex: Knex): Promise { await knex.raw(` UPDATE "${TABLE}" s - SET "folderId" = sub.folder_id + SET "folderId" = sub.folderId FROM ( SELECT DISTINCT ON ("secretSyncId") "secretSyncId", "folderId" FROM "${JOIN_TABLE}" diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index 9ddcfe0e4f..6343ebae8e 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -149,30 +149,23 @@ export const secretSyncDALFactory = ( data: Parameters<(typeof secretSyncOrm)["create"]>[0], folderIds?: Parameters<(typeof secretSyncOrmWithFolder)["insertMany"]>[0] ) => { - const secretSync = (await secretSyncOrm.transaction(async (tx) => { - const sync = await secretSyncOrm.create(data, tx); - - return baseSecretSyncQuery({ - filter: { id: sync.id }, - db, - tx - }).first(); - }))!; - const secretSyncId = secretSync.id; - - await secretSyncOrmWithFolder.transaction(async (tx) => { + const secretSync = await secretSyncOrm.transaction(async (tx) => { + const sync = await secretSyncOrm.create(data, tx); + if (folderIds && folderIds.length > 0) { const folderData = folderIds.map((folderId: string) => ({ folderId, - secretSyncId + secretSyncId: sync.id })); await secretSyncOrmWithFolder.insertMany(folderData, tx); } + + return baseSecretSyncQuery({ filter: { id: sync.id }, db, tx }).first(); }); - const normalizedFolderIds = Array.isArray(folderIds) ? folderIds : [folderIds]; - + const normalizedFolderIds = Array.isArray(folderIds) ? folderIds : (folderIds ? [folderIds] : []); + // TODO (scott): replace with cached folder path once implemented const folderWithPath = normalizedFolderIds.length ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, normalizedFolderIds) @@ -255,5 +248,5 @@ export const secretSyncDALFactory = ( } }; - return { ...secretSyncOrm, ...secretSyncOrmWithFolder, deleteById, findById, findOne, find, create, updateById }; + return { ...secretSyncOrm, deleteById, findById, findOne, find, create, updateById }; }; diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts index dbfe463b25..9b0689c5f6 100644 --- a/backend/src/services/secret-sync/secret-sync-schemas.ts +++ b/backend/src/services/secret-sync/secret-sync-schemas.ts @@ -85,8 +85,12 @@ export const BaseSecretSyncSchema = ( destination: SecretSync, @@ -140,5 +144,6 @@ export const GenericUpdateSecretSyncFieldsSchema = f.path) + secretPath: Array.isArray(secretSync.folder) + ? secretSync.folder.map((f: { path: string }) => f.path) + : [] }) : ProjectPermissionSub.SecretSyncs ) @@ -267,7 +269,7 @@ export const secretSyncServiceFactory = ({ folderIds: (Array.isArray(foldersRaw) ? foldersRaw : [foldersRaw]).filter(Boolean).map((folder: any) => folder.id) }; - if (!result) + if (!result.folderIds.length) throw new BadRequestError({ message: `Could not find folder with path "${secretPath}" in environment "${environment}" for project with ID "${projectId}"` }); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx index 72caf30edf..5cb6e92572 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx @@ -86,7 +86,7 @@ export const SecretSyncSourceFields = () => { defaultValue={false} render={({ field: { value, onChange } }) => (
- Recursive + Sync subfolders recursively Date: Sun, 7 Sep 2025 16:58:45 +0530 Subject: [PATCH 5/6] lint --- backend/src/services/secret-sync/secret-sync-dal.ts | 9 ++++----- .../src/services/secret-sync/secret-sync-schemas.ts | 11 +++++------ .../src/services/secret-sync/secret-sync-service.ts | 4 +--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index 6343ebae8e..10665b69fe 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -149,10 +149,9 @@ export const secretSyncDALFactory = ( data: Parameters<(typeof secretSyncOrm)["create"]>[0], folderIds?: Parameters<(typeof secretSyncOrmWithFolder)["insertMany"]>[0] ) => { - const secretSync = await secretSyncOrm.transaction(async (tx) => { const sync = await secretSyncOrm.create(data, tx); - + if (folderIds && folderIds.length > 0) { const folderData = folderIds.map((folderId: string) => ({ folderId, @@ -160,12 +159,12 @@ export const secretSyncDALFactory = ( })); await secretSyncOrmWithFolder.insertMany(folderData, tx); } - + return baseSecretSyncQuery({ filter: { id: sync.id }, db, tx }).first(); }); - const normalizedFolderIds = Array.isArray(folderIds) ? folderIds : (folderIds ? [folderIds] : []); - + const normalizedFolderIds = Array.isArray(folderIds) ? folderIds : folderIds ? [folderIds] : []; + // TODO (scott): replace with cached folder path once implemented const folderWithPath = normalizedFolderIds.length ? await folderDAL.findSecretPathByFolderIds(secretSync.projectId, normalizedFolderIds) diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts index 9b0689c5f6..c8afc46592 100644 --- a/backend/src/services/secret-sync/secret-sync-schemas.ts +++ b/backend/src/services/secret-sync/secret-sync-schemas.ts @@ -85,12 +85,11 @@ export const BaseSecretSyncSchema = ( destination: SecretSync, diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 5441bb1880..3c70567369 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -95,9 +95,7 @@ export const secretSyncServiceFactory = ({ secretSync.environment && secretSync.folder ? subject(ProjectPermissionSub.SecretSyncs, { environment: secretSync.environment.slug, - secretPath: Array.isArray(secretSync.folder) - ? secretSync.folder.map((f: { path: string }) => f.path) - : [] + secretPath: Array.isArray(secretSync.folder) ? secretSync.folder.map((f: { path: string }) => f.path) : [] }) : ProjectPermissionSub.SecretSyncs ) From 35ebe95c12d97dea0560c1cc42a730d89b5dd50f Mon Sep 17 00:00:00 2001 From: prayansh_chhablani Date: Tue, 9 Sep 2025 18:50:29 +0530 Subject: [PATCH 6/6] add recursive to update --- .../routes/v1/secret-sync-routers/secret-sync-endpoints.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts index 0dc58bfb26..8ccf8f32d8 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts @@ -40,6 +40,7 @@ export const registerSyncSecretsEndpoints = ; responseSchema: z.ZodTypeAny; }) => {