mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 14:27:30 +00:00
Add PIT rollback
This commit is contained in:
@@ -1,12 +1,15 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
||||||
|
|
||||||
import { ProjectType, TableName } from "../schemas";
|
import { ProjectType, TableName } from "../schemas";
|
||||||
import { getMigrationPITServices } from "./utils/services";
|
import { getMigrationPITServices } from "./utils/services";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
const hasFolderCommitTable = await knex.schema.hasTable(TableName.FolderCommit);
|
const hasFolderCommitTable = await knex.schema.hasTable(TableName.FolderCommit);
|
||||||
if (hasFolderCommitTable) {
|
if (hasFolderCommitTable) {
|
||||||
const { folderCommitService } = await getMigrationPITServices({ db: knex });
|
const keyStore = inMemoryKeyStore();
|
||||||
|
const { folderCommitService } = await getMigrationPITServices({ db: knex, keyStore });
|
||||||
const projects = await knex(TableName.Project).where({ version: 3, type: ProjectType.SecretManager }).select("id");
|
const projects = await knex(TableName.Project).where({ version: 3, type: ProjectType.SecretManager }).select("id");
|
||||||
await knex.transaction(async (tx) => {
|
await knex.transaction(async (tx) => {
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { orgDALFactory } from "@app/services/org/org-dal";
|
|||||||
import { projectDALFactory } from "@app/services/project/project-dal";
|
import { projectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
||||||
|
import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { userDALFactory } from "@app/services/user/user-dal";
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }
|
|||||||
return { kmsService };
|
return { kmsService };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMigrationPITServices = async ({ db }: { db: Knex }) => {
|
export const getMigrationPITServices = async ({ db, keyStore }: { db: Knex; keyStore: TKeyStoreFactory }) => {
|
||||||
const projectDAL = projectDALFactory(db);
|
const projectDAL = projectDALFactory(db);
|
||||||
const folderCommitDAL = folderCommitDALFactory(db);
|
const folderCommitDAL = folderCommitDALFactory(db);
|
||||||
const folderCommitChangesDAL = folderCommitChangesDALFactory(db);
|
const folderCommitChangesDAL = folderCommitChangesDALFactory(db);
|
||||||
@@ -74,6 +75,7 @@ export const getMigrationPITServices = async ({ db }: { db: Knex }) => {
|
|||||||
const folderVersionDAL = secretFolderVersionDALFactory(db);
|
const folderVersionDAL = secretFolderVersionDALFactory(db);
|
||||||
const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db);
|
const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db);
|
||||||
const folderCheckpointResourcesDAL = folderCheckpointResourcesDALFactory(db);
|
const folderCheckpointResourcesDAL = folderCheckpointResourcesDALFactory(db);
|
||||||
|
const secretV2BridgeDAL = secretV2BridgeDALFactory({ db, keyStore });
|
||||||
|
|
||||||
const folderCommitService = folderCommitServiceFactory({
|
const folderCommitService = folderCommitServiceFactory({
|
||||||
folderCommitDAL,
|
folderCommitDAL,
|
||||||
@@ -86,7 +88,8 @@ export const getMigrationPITServices = async ({ db }: { db: Knex }) => {
|
|||||||
folderVersionDAL,
|
folderVersionDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
folderCheckpointResourcesDAL
|
folderCheckpointResourcesDAL,
|
||||||
|
secretV2BridgeDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
return { folderCommitService };
|
return { folderCommitService };
|
||||||
|
|||||||
@@ -24,4 +24,38 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
|
|||||||
return backup;
|
return backup;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/rollback",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
fromCommit: z.string().trim(),
|
||||||
|
toCommit: z.string().trim(),
|
||||||
|
folderId: z.string().trim(),
|
||||||
|
projectId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.any()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const diff = await server.services.folderCommit.compareFolderStates(req.body.fromCommit, req.body.toCommit);
|
||||||
|
const response = await server.services.folderCommit.applyFolderStateDifferences(
|
||||||
|
diff,
|
||||||
|
{
|
||||||
|
actorType: req.permission?.type || "PLATFORM",
|
||||||
|
actorId: req.permission?.id,
|
||||||
|
message: "Rollback to previous commit"
|
||||||
|
},
|
||||||
|
req.body.folderId,
|
||||||
|
req.body.projectId
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -574,7 +574,8 @@ export const registerRoutes = async (
|
|||||||
folderVersionDAL,
|
folderVersionDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
folderCheckpointResourcesDAL
|
folderCheckpointResourcesDAL,
|
||||||
|
secretV2BridgeDAL
|
||||||
});
|
});
|
||||||
const scimService = scimServiceFactory({
|
const scimService = scimServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { TIdentityDALFactory } from "../identity/identity-dal";
|
|||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
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 { 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";
|
||||||
@@ -38,14 +39,33 @@ type TFolderCommitServiceFactoryDep = {
|
|||||||
folderTreeCheckpointDAL: Pick<TFolderTreeCheckpointDALFactory, "findByProjectId" | "findLatestByProjectId">;
|
folderTreeCheckpointDAL: Pick<TFolderTreeCheckpointDALFactory, "findByProjectId" | "findLatestByProjectId">;
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findByParentId" | "findByProjectId">;
|
folderDAL: Pick<
|
||||||
|
TSecretFolderDALFactory,
|
||||||
|
"findByParentId" | "findByProjectId" | "deleteById" | "create" | "updateById" | "update"
|
||||||
|
>;
|
||||||
folderVersionDAL: Pick<
|
folderVersionDAL: Pick<
|
||||||
TSecretFolderVersionDALFactory,
|
TSecretFolderVersionDALFactory,
|
||||||
"findLatestFolderVersions" | "findById" | "deleteById" | "create" | "updateById"
|
| "findLatestFolderVersions"
|
||||||
|
| "findById"
|
||||||
|
| "deleteById"
|
||||||
|
| "create"
|
||||||
|
| "updateById"
|
||||||
|
| "find"
|
||||||
|
| "findByIdsWithLatestVersion"
|
||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<
|
secretVersionV2BridgeDAL: Pick<
|
||||||
TSecretVersionV2DALFactory,
|
TSecretVersionV2DALFactory,
|
||||||
"findLatestVersionByFolderId" | "findById" | "deleteById" | "create" | "updateById"
|
| "findLatestVersionByFolderId"
|
||||||
|
| "findById"
|
||||||
|
| "deleteById"
|
||||||
|
| "create"
|
||||||
|
| "updateById"
|
||||||
|
| "find"
|
||||||
|
| "findByIdsWithLatestVersion"
|
||||||
|
>;
|
||||||
|
secretV2BridgeDAL: Pick<
|
||||||
|
TSecretV2BridgeDALFactory,
|
||||||
|
"deleteById" | "create" | "updateById" | "update" | "insertMany" | "invalidateSecretCacheByProjectId"
|
||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
@@ -85,7 +105,8 @@ export const folderCommitServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
folderVersionDAL,
|
folderVersionDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
secretV2BridgeDAL
|
||||||
}: TFolderCommitServiceFactoryDep) => {
|
}: TFolderCommitServiceFactoryDep) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
@@ -356,6 +377,261 @@ export const folderCommitServiceFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applyFolderStateDifferences = async (
|
||||||
|
differences: Array<{
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
versionId: string;
|
||||||
|
oldVersionId?: string;
|
||||||
|
changeType: "create" | "update" | "delete";
|
||||||
|
}>,
|
||||||
|
actorInfo: {
|
||||||
|
actorType: string;
|
||||||
|
actorId?: string;
|
||||||
|
message?: string;
|
||||||
|
},
|
||||||
|
folderId: string,
|
||||||
|
projectId: string
|
||||||
|
) => {
|
||||||
|
let result = {};
|
||||||
|
await folderCommitDAL.transaction(async (tx) => {
|
||||||
|
// Group differences by type for more efficient processing
|
||||||
|
const secretChanges = differences.filter((diff) => diff.type === "secret");
|
||||||
|
const folderChanges = differences.filter((diff) => diff.type === "folder");
|
||||||
|
|
||||||
|
const secretVersions = await secretVersionV2BridgeDAL.findByIdsWithLatestVersion(
|
||||||
|
folderId,
|
||||||
|
secretChanges.map((diff) => diff.id),
|
||||||
|
secretChanges.map((diff) => diff.versionId)
|
||||||
|
);
|
||||||
|
const folderVersions = await folderVersionDAL.findByIdsWithLatestVersion(
|
||||||
|
folderChanges.map((diff) => diff.id),
|
||||||
|
folderChanges.map((diff) => diff.versionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Track all changes for commit recording
|
||||||
|
const commitChanges = [];
|
||||||
|
|
||||||
|
// Process secret changes
|
||||||
|
for (const change of secretChanges) {
|
||||||
|
const secretVersion = secretVersions[change.id];
|
||||||
|
switch (change.changeType) {
|
||||||
|
case "create":
|
||||||
|
if (secretVersion) {
|
||||||
|
const newSecret = [
|
||||||
|
{
|
||||||
|
id: change.id,
|
||||||
|
skipMultilineEncoding: secretVersion.skipMultilineEncoding,
|
||||||
|
version: secretVersion.version + 1,
|
||||||
|
type: secretVersion.type,
|
||||||
|
key: secretVersion.key,
|
||||||
|
reminderNote: secretVersion.reminderNote,
|
||||||
|
reminderRepeatDays: secretVersion.reminderRepeatDays,
|
||||||
|
encryptedValue: secretVersion.encryptedValue,
|
||||||
|
encryptedComment: secretVersion.encryptedComment,
|
||||||
|
userId: secretVersion.userId,
|
||||||
|
metadata: secretVersion.metadata,
|
||||||
|
folderId
|
||||||
|
}
|
||||||
|
];
|
||||||
|
await secretV2BridgeDAL.insertMany(newSecret, tx);
|
||||||
|
|
||||||
|
const newVersion = await secretVersionV2BridgeDAL.create(
|
||||||
|
{
|
||||||
|
folderId,
|
||||||
|
secretId: secretVersion.secretId,
|
||||||
|
version: secretVersion.version + 1,
|
||||||
|
encryptedValue: secretVersion.encryptedValue,
|
||||||
|
key: secretVersion.key,
|
||||||
|
encryptedComment: secretVersion.encryptedComment,
|
||||||
|
skipMultilineEncoding: secretVersion.skipMultilineEncoding,
|
||||||
|
reminderNote: secretVersion.reminderNote,
|
||||||
|
reminderRepeatDays: secretVersion.reminderRepeatDays,
|
||||||
|
userId: secretVersion.userId,
|
||||||
|
metadata: secretVersion.metadata,
|
||||||
|
actorType: actorInfo.actorType,
|
||||||
|
envId: secretVersion.envId,
|
||||||
|
...(actorInfo.actorType === ActorType.IDENTITY && { identityActorId: actorInfo.actorId }),
|
||||||
|
...(actorInfo.actorType === ActorType.USER && { userActorId: actorInfo.actorId })
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "add",
|
||||||
|
secretVersionId: newVersion.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
// Update secret to specific version
|
||||||
|
if (secretVersion) {
|
||||||
|
await secretV2BridgeDAL.updateById(
|
||||||
|
change.id,
|
||||||
|
{
|
||||||
|
skipMultilineEncoding: secretVersion?.skipMultilineEncoding,
|
||||||
|
version: secretVersion?.version,
|
||||||
|
type: secretVersion?.type,
|
||||||
|
key: secretVersion?.key,
|
||||||
|
reminderNote: secretVersion?.reminderNote,
|
||||||
|
reminderRepeatDays: secretVersion?.reminderRepeatDays,
|
||||||
|
encryptedValue: secretVersion?.encryptedValue,
|
||||||
|
encryptedComment: secretVersion?.encryptedComment,
|
||||||
|
userId: secretVersion?.userId,
|
||||||
|
metadata: secretVersion?.metadata
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const newVersion = await secretVersionV2BridgeDAL.create(
|
||||||
|
{
|
||||||
|
version: secretVersion.version + 1,
|
||||||
|
encryptedValue: secretVersion.encryptedValue,
|
||||||
|
key: secretVersion.key,
|
||||||
|
encryptedComment: secretVersion.encryptedComment,
|
||||||
|
skipMultilineEncoding: secretVersion.skipMultilineEncoding,
|
||||||
|
reminderNote: secretVersion.reminderNote,
|
||||||
|
reminderRepeatDays: secretVersion.reminderRepeatDays,
|
||||||
|
userId: secretVersion.userId,
|
||||||
|
metadata: secretVersion.metadata,
|
||||||
|
actorType: actorInfo.actorType,
|
||||||
|
envId: secretVersion.envId,
|
||||||
|
folderId,
|
||||||
|
secretId: secretVersion.secretId,
|
||||||
|
...(actorInfo.actorType === ActorType.IDENTITY && { identityActorId: actorInfo.actorId }),
|
||||||
|
...(actorInfo.actorType === ActorType.USER && { userActorId: actorInfo.actorId })
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "add",
|
||||||
|
secretVersionId: newVersion.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "delete":
|
||||||
|
await secretV2BridgeDAL.deleteById(change.id, tx);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "delete",
|
||||||
|
secretVersionId: change.versionId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: `Unknown change type: ${change.changeType as string}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process folder changes
|
||||||
|
for (const change of folderChanges) {
|
||||||
|
const folderVersion = folderVersions[change.id];
|
||||||
|
switch (change.changeType) {
|
||||||
|
case "create":
|
||||||
|
// Add new folder
|
||||||
|
if (folderVersion) {
|
||||||
|
const newFolder = {
|
||||||
|
id: change.id,
|
||||||
|
parentId: folderId,
|
||||||
|
envId: folderVersion.envId,
|
||||||
|
version: (folderVersion.version || 1) + 1,
|
||||||
|
name: folderVersion.name
|
||||||
|
};
|
||||||
|
await folderDAL.create(newFolder, tx);
|
||||||
|
|
||||||
|
const newFolderVersion = await folderVersionDAL.create(
|
||||||
|
{
|
||||||
|
folderId: change.id,
|
||||||
|
version: (folderVersion.version || 1) + 1,
|
||||||
|
name: folderVersion.name,
|
||||||
|
envId: folderVersion.envId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "add",
|
||||||
|
folderVersionId: newFolderVersion.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
// Update folder to specific version
|
||||||
|
if (change.versionId) {
|
||||||
|
await folderVersionDAL.findById(change.versionId, tx).then(async (versionDetails) => {
|
||||||
|
if (versionDetails) {
|
||||||
|
await folderDAL.updateById(
|
||||||
|
change.id,
|
||||||
|
{
|
||||||
|
parentId: folderId,
|
||||||
|
envId: versionDetails.envId,
|
||||||
|
version: (versionDetails.version || 1) + 1,
|
||||||
|
name: versionDetails.name
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const newFolderVersion = await folderVersionDAL.create(
|
||||||
|
{
|
||||||
|
folderId: change.id,
|
||||||
|
version: (versionDetails.version || 1) + 1,
|
||||||
|
name: versionDetails.name,
|
||||||
|
envId: versionDetails.envId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "add",
|
||||||
|
folderVersionId: newFolderVersion.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "delete":
|
||||||
|
await folderDAL.deleteById(change.id, tx);
|
||||||
|
|
||||||
|
commitChanges.push({
|
||||||
|
type: "delete",
|
||||||
|
folderVersionId: change.versionId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: `Unknown change type: ${change.changeType as string}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await createCommit(
|
||||||
|
{
|
||||||
|
actor: {
|
||||||
|
type: actorInfo.actorType,
|
||||||
|
metadata: { id: actorInfo.actorId }
|
||||||
|
},
|
||||||
|
message: actorInfo.message || "Rolled back folder state",
|
||||||
|
folderId,
|
||||||
|
changes: commitChanges
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
result = {
|
||||||
|
secretChangesCount: secretChanges.length,
|
||||||
|
folderChangesCount: folderChanges.length,
|
||||||
|
totalChanges: differences.length
|
||||||
|
};
|
||||||
|
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
// Retrieve a commit by ID
|
// Retrieve a commit by ID
|
||||||
const getCommitById = async (id: string, tx?: Knex) => {
|
const getCommitById = async (id: string, tx?: Knex) => {
|
||||||
return folderCommitDAL.findById(id, tx);
|
return folderCommitDAL.findById(id, tx);
|
||||||
@@ -467,7 +743,8 @@ export const folderCommitServiceFactory = ({
|
|||||||
initializeFolder,
|
initializeFolder,
|
||||||
initializeProject,
|
initializeProject,
|
||||||
createFolderCheckpoint,
|
createFolderCheckpoint,
|
||||||
compareFolderStates
|
compareFolderStates,
|
||||||
|
applyFolderStateDifferences
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -96,5 +96,80 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
|||||||
logger.info(`${QueueName.DailyResourceCleanUp}: pruning secret folder versions completed`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: pruning secret folder versions completed`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId, pruneExcessVersions };
|
const findByIdsWithLatestVersion = async (folderIds: string[], versionIds?: string[], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
if (!folderIds.length && (!versionIds || !versionIds.length)) return {};
|
||||||
|
|
||||||
|
const knexInstance = tx || db.replicaNode();
|
||||||
|
let allDocs: Array<TSecretFolderVersions & { max?: number }> = [];
|
||||||
|
|
||||||
|
// Process latest versions for folderIds
|
||||||
|
if (folderIds.length) {
|
||||||
|
const latestVersions: Array<TSecretFolderVersions & { max: number }> = await knexInstance(
|
||||||
|
TableName.SecretFolderVersion
|
||||||
|
)
|
||||||
|
.whereIn(`${TableName.SecretFolderVersion}.folderId`, folderIds)
|
||||||
|
.join(
|
||||||
|
knexInstance(TableName.SecretFolderVersion)
|
||||||
|
.groupBy("folderId")
|
||||||
|
.max("version")
|
||||||
|
.select("folderId")
|
||||||
|
.as("latestVersion"),
|
||||||
|
(bd) => {
|
||||||
|
bd.on(`${TableName.SecretFolderVersion}.folderId`, "latestVersion.folderId").andOn(
|
||||||
|
`${TableName.SecretFolderVersion}.version`,
|
||||||
|
"latestVersion.max"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
allDocs = [...allDocs, ...latestVersions];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process specific versions by versionIds
|
||||||
|
if (versionIds && versionIds.length) {
|
||||||
|
// Get the specific versions
|
||||||
|
const specificVersions = await knexInstance(TableName.SecretFolderVersion).whereIn("id", versionIds);
|
||||||
|
|
||||||
|
// Get the folderIds from these versions
|
||||||
|
const specificFolderIds = [...new Set(specificVersions.map((v) => v.folderId).filter(Boolean))];
|
||||||
|
|
||||||
|
// Get max versions for these folderIds
|
||||||
|
const maxVersionsQuery = (await knexInstance(TableName.SecretFolderVersion)
|
||||||
|
.whereIn("folderId", specificFolderIds)
|
||||||
|
.groupBy("folderId")
|
||||||
|
.select("folderId")
|
||||||
|
.max("version as max")) as Array<{ folderId: string; max: number }>;
|
||||||
|
|
||||||
|
// Create a lookup map for max versions
|
||||||
|
const maxVersionMap = maxVersionsQuery.reduce<Record<string, number>>((acc, item) => {
|
||||||
|
acc[item.folderId] = item.max;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Add max version to each specific version
|
||||||
|
const specificVersionsWithMax = specificVersions.map((version) => ({
|
||||||
|
...version,
|
||||||
|
max: maxVersionMap[version.folderId]
|
||||||
|
}));
|
||||||
|
|
||||||
|
allDocs = [...allDocs, ...specificVersionsWithMax];
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDocs.reduce<Record<string, TSecretFolderVersions & { max?: number }>>(
|
||||||
|
(prev, curr) => ({ ...prev, [curr.folderId || ""]: curr }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindByIdsWithLatestVersion" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...secretFolderVerOrm,
|
||||||
|
findLatestFolderVersions,
|
||||||
|
findLatestVersionByFolderId,
|
||||||
|
pruneExcessVersions,
|
||||||
|
findByIdsWithLatestVersion
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindLatestVersinMany" });
|
throw new DatabaseError({ error, name: "FindLatestVersionMany" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -261,6 +261,84 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findByIdsWithLatestVersion = async (
|
||||||
|
folderId: string,
|
||||||
|
secretIds: string[],
|
||||||
|
versionIds?: string[],
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (!secretIds.length && (!versionIds || !versionIds.length)) return {};
|
||||||
|
|
||||||
|
const knexInstance = tx || db.replicaNode();
|
||||||
|
let allDocs: Array<TSecretVersionsV2 & { max?: number }> = [];
|
||||||
|
|
||||||
|
// Process latest versions for secretIds
|
||||||
|
if (secretIds.length) {
|
||||||
|
const latestVersions: Array<TSecretVersionsV2 & { max: number }> = await knexInstance(TableName.SecretVersionV2)
|
||||||
|
.where("folderId", folderId)
|
||||||
|
.whereIn(`${TableName.SecretVersionV2}.secretId`, secretIds)
|
||||||
|
.join(
|
||||||
|
knexInstance(TableName.SecretVersionV2)
|
||||||
|
.groupBy("secretId")
|
||||||
|
.max("version")
|
||||||
|
.select("secretId")
|
||||||
|
.as("latestVersion"),
|
||||||
|
(bd) => {
|
||||||
|
bd.on(`${TableName.SecretVersionV2}.secretId`, "latestVersion.secretId").andOn(
|
||||||
|
`${TableName.SecretVersionV2}.version`,
|
||||||
|
"latestVersion.max"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
allDocs = [...allDocs, ...latestVersions];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process specific versions by versionIds
|
||||||
|
if (versionIds && versionIds.length) {
|
||||||
|
// Get the specific versions
|
||||||
|
const specificVersions = await knexInstance(TableName.SecretVersionV2)
|
||||||
|
.where("folderId", folderId)
|
||||||
|
.whereIn("id", versionIds);
|
||||||
|
|
||||||
|
// Get the secretIds from these versions
|
||||||
|
const specificSecretIds = [...new Set(specificVersions.map((v) => v.secretId).filter(Boolean))];
|
||||||
|
|
||||||
|
// Get max versions for these secretIds
|
||||||
|
const maxVersionsQuery = (await knexInstance(TableName.SecretVersionV2)
|
||||||
|
.whereIn("secretId", specificSecretIds)
|
||||||
|
.groupBy("secretId")
|
||||||
|
.select("secretId")
|
||||||
|
.max("version as max")) as Array<{ secretId: string; max: number }>;
|
||||||
|
|
||||||
|
// Create a lookup map for max versions
|
||||||
|
const maxVersionMap = maxVersionsQuery.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.secretId] = item.max;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, number>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add max version to each specific version
|
||||||
|
const specificVersionsWithMax = specificVersions.map((version) => ({
|
||||||
|
...version,
|
||||||
|
max: maxVersionMap[version.secretId]
|
||||||
|
}));
|
||||||
|
|
||||||
|
allDocs = [...allDocs, ...specificVersionsWithMax];
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDocs.reduce<Record<string, TSecretVersionsV2 & { max?: number }>>(
|
||||||
|
(prev, curr) => ({ ...prev, [curr.secretId || ""]: curr }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindByIdsWithLatestVersion" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...secretVersionV2Orm,
|
...secretVersionV2Orm,
|
||||||
pruneExcessVersions,
|
pruneExcessVersions,
|
||||||
@@ -268,6 +346,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId,
|
findLatestVersionByFolderId,
|
||||||
findVersionsBySecretIdWithActors,
|
findVersionsBySecretIdWithActors,
|
||||||
findBySecretId
|
findBySecretId,
|
||||||
|
findByIdsWithLatestVersion
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user