PIT: Add delete commit for cascade deletion

This commit is contained in:
carlosmonastyrski
2025-05-26 21:51:43 -03:00
parent f3a84f6001
commit d36d0784ca
6 changed files with 130 additions and 7 deletions

View File

@@ -415,6 +415,7 @@ export const secretSnapshotServiceFactory = ({
secretVersionId?: string; secretVersionId?: string;
folderVersionId?: string; folderVersionId?: string;
isUpdate?: boolean; isUpdate?: boolean;
folderId?: string;
}[] = []; }[] = [];
// this will remove all secrets in current folder except rotated secrets which we ignore // this will remove all secrets in current folder except rotated secrets which we ignore
@@ -625,7 +626,8 @@ export const secretSnapshotServiceFactory = ({
// Folder was only deleted // Folder was only deleted
commitChanges.push({ commitChanges.push({
type: CommitType.DELETE, type: CommitType.DELETE,
folderVersionId: deletedInfo.versionId folderVersionId: deletedInfo.versionId,
folderId: deletedInfo.id
}); });
} }
}); });

View File

@@ -30,7 +30,7 @@ import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metad
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal"; import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import * as secretV2BridgeDal from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TUserDALFactory } from "../user/user-dal"; import { TUserDALFactory } from "../user/user-dal";
import { TFolderCommitDALFactory } from "./folder-commit-dal"; import { TFolderCommitDALFactory } from "./folder-commit-dal";
@@ -68,6 +68,7 @@ type TCreateCommitDTO = {
secretVersionId?: string; secretVersionId?: string;
folderVersionId?: string; folderVersionId?: string;
isUpdate?: boolean; isUpdate?: boolean;
folderId?: string;
}[]; }[];
}; };
@@ -143,7 +144,7 @@ type TFolderCommitServiceFactoryDep = {
folderDAL: TSecretFolderDALFactory; folderDAL: TSecretFolderDALFactory;
folderVersionDAL: TSecretFolderVersionDALFactory; folderVersionDAL: TSecretFolderVersionDALFactory;
secretVersionV2BridgeDAL: TSecretVersionV2DALFactory; secretVersionV2BridgeDAL: TSecretVersionV2DALFactory;
secretV2BridgeDAL: secretV2BridgeDal.TSecretV2BridgeDALFactory; secretV2BridgeDAL: TSecretV2BridgeDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectByEnvId">; projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectByEnvId">;
folderCommitQueueService?: Pick< folderCommitQueueService?: Pick<
TFolderCommitQueueServiceFactory, TFolderCommitQueueServiceFactory,
@@ -676,6 +677,95 @@ export const folderCommitServiceFactory = ({
} }
}; };
const createDeleteCommitForNestedFolders = async ({
folderId,
actorMetadata,
actorType,
envId,
parentFolderName,
step = 1,
tx
}: {
folderId: string;
actorMetadata: Record<string, string>;
actorType: string;
envId: string;
parentFolderName: string;
step?: number;
tx?: Knex;
}) => {
if (step > 20) {
logger.info(`createDeleteCommitForNestedFolders - Max step reached for folder ${folderId}`);
return;
}
logger.info(`Creating delete commit for nested folders ${folderId}`);
const folderVersion = await folderVersionDAL.findLatestVersion(folderId, tx);
if (!folderVersion) {
logger.info(`No folder version found for ${folderId}`);
return;
}
const lastFolderCommit = await folderCommitDAL.findLatestCommit(folderId, undefined, tx);
if (!lastFolderCommit) {
logger.info(`No commit found for folder ${folderId}`);
return;
}
const folderState = await reconstructFolderState(lastFolderCommit.id, tx);
const changes = folderState.map((resource) => ({
type: ChangeType.DELETE,
folderId: resource.id,
folderName: resource.folderName,
secretVersionId: resource.type === ResourceType.SECRET ? resource.versionId : undefined,
folderVersionId: resource.type === ResourceType.FOLDER ? resource.versionId : undefined,
secretKey: resource.secretKey
}));
logger.info(`Found ${changes.length} changes for ${folderId}`);
await Promise.all(
changes
.filter((change) => change.type === ChangeType.DELETE && change.folderVersionId)
.map(async (change) => {
await createDeleteCommitForNestedFolders({
folderId: change.folderId,
actorMetadata,
actorType,
envId,
parentFolderName: folderVersion.name,
step: step + 1,
tx
});
})
);
const newCommit = await folderCommitDAL.create(
{
actorMetadata,
actorType,
message: `Parent folder ${parentFolderName} deleted`,
folderId,
envId
},
tx
);
const batchSize = 500;
const chunks = chunkArray(changes, batchSize);
await Promise.all(
chunks.map(async (chunk) => {
await folderCommitChangesDAL.insertMany(
chunk.map((change) => ({
folderCommitId: newCommit.id,
changeType: CommitType.DELETE,
secretVersionId: change.secretVersionId,
folderVersionId: change.folderVersionId,
isUpdate: false
})),
tx
);
})
);
};
/** /**
* Creates a new commit with the provided changes * Creates a new commit with the provided changes
*/ */
@@ -698,6 +788,21 @@ export const folderCommitServiceFactory = ({
throw new NotFoundError({ message: `Folder with ID ${data.folderId} not found` }); throw new NotFoundError({ message: `Folder with ID ${data.folderId} not found` });
} }
await Promise.all(
data.changes.map(async (change) => {
if (change.type === ChangeType.DELETE && change.folderId) {
await createDeleteCommitForNestedFolders({
folderId: change.folderId,
actorMetadata: metadata,
actorType: data.actor.type,
envId: folder.envId,
parentFolderName: folder.name,
tx
});
}
})
);
const newCommit = await folderCommitDAL.create( const newCommit = await folderCommitDAL.create(
{ {
actorMetadata: metadata, actorMetadata: metadata,
@@ -1102,7 +1207,8 @@ export const folderCommitServiceFactory = ({
commitChanges.push({ commitChanges.push({
type: ChangeType.DELETE, type: ChangeType.DELETE,
folderVersionId: change.versionId folderVersionId: change.versionId,
folderId: change.id
}); });
break; break;

View File

@@ -537,6 +537,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
try { try {
const folders = await (tx || db.replicaNode())(TableName.SecretFolder) const folders = await (tx || db.replicaNode())(TableName.SecretFolder)
.where({ parentId }) .where({ parentId })
.andWhere({ isReserved: false })
.select(selectAllTableCols(TableName.SecretFolder)); .select(selectAllTableCols(TableName.SecretFolder));
return folders; return folders;
} catch (error) { } catch (error) {

View File

@@ -478,7 +478,8 @@ export const secretFolderServiceFactory = ({
changes: [ changes: [
{ {
type: CommitType.DELETE, type: CommitType.DELETE,
folderVersionId: folderVersions[doc.id].id folderVersionId: folderVersions[doc.id].id,
folderId: doc.id
} }
] ]
}, },

View File

@@ -180,11 +180,24 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
} }
}; };
const findLatestVersion = async (folderId: string, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.SecretFolderVersion)
.where(`${TableName.SecretFolderVersion}.folderId`, folderId)
.select(selectAllTableCols(TableName.SecretFolderVersion))
.first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "findLatestVersion" });
}
};
return { return {
...secretFolderVerOrm, ...secretFolderVerOrm,
findLatestFolderVersions, findLatestFolderVersions,
findLatestVersionByFolderId, findLatestVersionByFolderId,
pruneExcessVersions, pruneExcessVersions,
findByIdsWithLatestVersion findByIdsWithLatestVersion,
findLatestVersion
}; };
}; };

View File

@@ -12,8 +12,8 @@ import {
} from "@app/context"; } from "@app/context";
import { import {
PermissionConditionOperators, PermissionConditionOperators,
ProjectPermissionCommitsActions,
ProjectPermissionApprovalActions, ProjectPermissionApprovalActions,
ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions, ProjectPermissionDynamicSecretActions,
ProjectPermissionGroupActions, ProjectPermissionGroupActions,
ProjectPermissionIdentityActions, ProjectPermissionIdentityActions,