mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 14:34:09 +00:00
Revert "feat(PIT): Point In Time Revamp"
This commit is contained in:
@@ -10,7 +10,6 @@ import { chunkArray } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { CommitType, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
@@ -19,7 +18,6 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
||||
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { fnSecretBulkInsert, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||
@@ -44,8 +42,6 @@ export type TImportDataIntoInfisicalDTO = {
|
||||
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
folderVersionDAL: Pick<TSecretFolderVersionDALFactory, "create">;
|
||||
|
||||
input: TImportInfisicalDataCreate;
|
||||
};
|
||||
@@ -511,8 +507,6 @@ export const importDataIntoInfisicalFn = async ({
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
resourceMetadataDAL,
|
||||
folderVersionDAL,
|
||||
folderCommitService,
|
||||
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
|
||||
}: TImportDataIntoInfisicalDTO) => {
|
||||
// Import data to infisical
|
||||
@@ -605,36 +599,6 @@ export const importDataIntoInfisicalFn = async ({
|
||||
tx
|
||||
);
|
||||
|
||||
const newFolderVersion = await folderVersionDAL.create(
|
||||
{
|
||||
name: newFolder.name,
|
||||
envId: newFolder.envId,
|
||||
version: newFolder.version,
|
||||
folderId: newFolder.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Changed by external migration",
|
||||
folderId: parentEnv.rootFolderId,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
folderVersionId: newFolderVersion.id
|
||||
}
|
||||
]
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
originalToNewFolderId.set(folder.id, {
|
||||
folderId: newFolder.id,
|
||||
projectId: parentEnv.projectId
|
||||
@@ -808,7 +772,6 @@ export const importDataIntoInfisicalFn = async ({
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderCommitService,
|
||||
actor: {
|
||||
type: actor,
|
||||
actorId
|
||||
|
||||
@@ -3,7 +3,6 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectServiceFactory } from "../project/project-service";
|
||||
@@ -11,7 +10,6 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
||||
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
|
||||
@@ -38,8 +36,6 @@ export type TExternalMigrationQueueFactoryDep = {
|
||||
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
folderVersionDAL: Pick<TSecretFolderVersionDALFactory, "create">;
|
||||
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
};
|
||||
@@ -60,8 +56,6 @@ export const externalMigrationQueueFactory = ({
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderDAL,
|
||||
folderCommitService,
|
||||
folderVersionDAL,
|
||||
resourceMetadataDAL
|
||||
}: TExternalMigrationQueueFactoryDep) => {
|
||||
const startImport = async (dto: {
|
||||
@@ -120,8 +114,6 @@ export const externalMigrationQueueFactory = ({
|
||||
projectService,
|
||||
projectEnvService,
|
||||
secretV2BridgeService,
|
||||
folderCommitService,
|
||||
folderVersionDAL,
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TFolderCheckpointResources,
|
||||
TFolderCheckpoints,
|
||||
TSecretFolderVersions,
|
||||
TSecretVersionsV2
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderCheckpointResourcesDALFactory = ReturnType<typeof folderCheckpointResourcesDALFactory>;
|
||||
|
||||
export type ResourceWithCheckpointInfo = TFolderCheckpointResources & {
|
||||
folderCommitId: string;
|
||||
};
|
||||
|
||||
export const folderCheckpointResourcesDALFactory = (db: TDbClient) => {
|
||||
const folderCheckpointResourcesOrm = ormify(db, TableName.FolderCheckpointResources);
|
||||
|
||||
const findByCheckpointId = async (
|
||||
folderCheckpointId: string,
|
||||
tx?: Knex
|
||||
): Promise<
|
||||
(TFolderCheckpointResources & {
|
||||
referencedSecretId?: string;
|
||||
referencedFolderId?: string;
|
||||
folderName?: string;
|
||||
folderVersion?: string;
|
||||
secretKey?: string;
|
||||
secretVersion?: string;
|
||||
})[]
|
||||
> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<TFolderCheckpointResources>(TableName.FolderCheckpointResources)
|
||||
.where({ folderCheckpointId })
|
||||
.leftJoin<TSecretVersionsV2>(
|
||||
TableName.SecretVersionV2,
|
||||
`${TableName.FolderCheckpointResources}.secretVersionId`,
|
||||
`${TableName.SecretVersionV2}.id`
|
||||
)
|
||||
.leftJoin<TSecretFolderVersions>(
|
||||
TableName.SecretFolderVersion,
|
||||
`${TableName.FolderCheckpointResources}.folderVersionId`,
|
||||
`${TableName.SecretFolderVersion}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.FolderCheckpointResources))
|
||||
.select(
|
||||
db.ref("secretId").withSchema(TableName.SecretVersionV2).as("referencedSecretId"),
|
||||
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("referencedFolderId"),
|
||||
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderName"),
|
||||
db.ref("version").withSchema(TableName.SecretFolderVersion).as("folderVersion"),
|
||||
db.ref("key").withSchema(TableName.SecretVersionV2).as("secretKey"),
|
||||
db.ref("version").withSchema(TableName.SecretVersionV2).as("secretVersion")
|
||||
);
|
||||
return docs.map((doc) => ({
|
||||
...doc,
|
||||
folderVersion: doc.folderVersion?.toString(),
|
||||
secretVersion: doc.secretVersion?.toString()
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByCheckpointId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findBySecretVersionId = async (secretVersionId: string, tx?: Knex): Promise<ResourceWithCheckpointInfo[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<
|
||||
TFolderCheckpointResources & Pick<TFolderCheckpoints, "folderCommitId" | "createdAt">
|
||||
>(TableName.FolderCheckpointResources)
|
||||
.where({ secretVersionId })
|
||||
.select(selectAllTableCols(TableName.FolderCheckpointResources))
|
||||
.join(
|
||||
TableName.FolderCheckpoint,
|
||||
`${TableName.FolderCheckpointResources}.folderCheckpointId`,
|
||||
`${TableName.FolderCheckpoint}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("folderCommitId").withSchema(TableName.FolderCheckpoint),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCheckpoint)
|
||||
);
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindBySecretVersionId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByFolderVersionId = async (folderVersionId: string, tx?: Knex): Promise<ResourceWithCheckpointInfo[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<
|
||||
TFolderCheckpointResources & Pick<TFolderCheckpoints, "folderCommitId" | "createdAt">
|
||||
>(TableName.FolderCheckpointResources)
|
||||
.where({ folderVersionId })
|
||||
.select(selectAllTableCols(TableName.FolderCheckpointResources))
|
||||
.join(
|
||||
TableName.FolderCheckpoint,
|
||||
`${TableName.FolderCheckpointResources}.folderCheckpointId`,
|
||||
`${TableName.FolderCheckpoint}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("folderCommitId").withSchema(TableName.FolderCheckpoint),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCheckpoint)
|
||||
);
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByFolderVersionId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...folderCheckpointResourcesOrm,
|
||||
findByCheckpointId,
|
||||
findBySecretVersionId,
|
||||
findByFolderVersionId
|
||||
};
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TFolderCheckpoints, TFolderCommits } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderCheckpointDALFactory = ReturnType<typeof folderCheckpointDALFactory>;
|
||||
|
||||
type CheckpointWithCommitInfo = TFolderCheckpoints & {
|
||||
actorMetadata: unknown;
|
||||
actorType: string;
|
||||
message?: string | null;
|
||||
commitDate: Date;
|
||||
folderId: string;
|
||||
};
|
||||
|
||||
export const folderCheckpointDALFactory = (db: TDbClient) => {
|
||||
const folderCheckpointOrm = ormify(db, TableName.FolderCheckpoint);
|
||||
|
||||
const findByCommitId = async (folderCommitId: string, tx?: Knex): Promise<TFolderCheckpoints | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())<TFolderCheckpoints>(TableName.FolderCheckpoint)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderCommitId }, TableName.FolderCheckpoint))
|
||||
.select(selectAllTableCols(TableName.FolderCheckpoint))
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByCommitId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByFolderId = async (folderId: string, limit?: number, tx?: Knex): Promise<CheckpointWithCommitInfo[]> => {
|
||||
try {
|
||||
let query = (tx || db.replicaNode())(TableName.FolderCheckpoint)
|
||||
.join<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderCheckpoint}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderId }, TableName.FolderCommit))
|
||||
.select(selectAllTableCols(TableName.FolderCheckpoint))
|
||||
.select(
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit).as("commitDate"),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit)
|
||||
)
|
||||
.orderBy(`${TableName.FolderCheckpoint}.createdAt`, "desc");
|
||||
|
||||
if (limit !== undefined) {
|
||||
query = query.limit(limit);
|
||||
}
|
||||
|
||||
return await query;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByFolderId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestByFolderId = async (folderId: string, tx?: Knex): Promise<CheckpointWithCommitInfo | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCheckpoint)
|
||||
.join<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderCheckpoint}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderId }, TableName.FolderCommit))
|
||||
.select(selectAllTableCols(TableName.FolderCheckpoint))
|
||||
.select(
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit).as("commitDate"),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit)
|
||||
)
|
||||
.orderBy(`${TableName.FolderCheckpoint}.createdAt`, "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestByFolderId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findNearestCheckpoint = async (
|
||||
folderCommitId: bigint,
|
||||
folderId: string,
|
||||
tx?: Knex
|
||||
): Promise<(CheckpointWithCommitInfo & { commitId: bigint }) | undefined> => {
|
||||
try {
|
||||
// Get the checkpoint with the highest commitId that's still less than or equal to our commit
|
||||
const nearestCheckpoint = await (tx || db.replicaNode())(TableName.FolderCheckpoint)
|
||||
.join<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderCheckpoint}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
.where(`${TableName.FolderCommit}.folderId`, "=", folderId)
|
||||
.where(`${TableName.FolderCommit}.commitId`, "<=", folderCommitId.toString())
|
||||
.select(selectAllTableCols(TableName.FolderCheckpoint))
|
||||
.select(
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("commitId").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit).as("commitDate"),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit)
|
||||
)
|
||||
.orderBy(`${TableName.FolderCommit}.commitId`, "desc")
|
||||
.first();
|
||||
return nearestCheckpoint;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindNearestCheckpoint" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...folderCheckpointOrm,
|
||||
findByCommitId,
|
||||
findByFolderId,
|
||||
findLatestByFolderId,
|
||||
findNearestCheckpoint
|
||||
};
|
||||
};
|
||||
@@ -1,233 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TFolderCommitChanges,
|
||||
TFolderCommits,
|
||||
TProjectEnvironments,
|
||||
TSecretFolderVersions,
|
||||
TSecretVersionsV2
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderCommitChangesDALFactory = ReturnType<typeof folderCommitChangesDALFactory>;
|
||||
|
||||
// Base type with common fields
|
||||
type BaseCommitChangeInfo = TFolderCommitChanges & {
|
||||
actorMetadata: unknown;
|
||||
actorType: string;
|
||||
message?: string | null;
|
||||
folderId: string;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
// Secret-specific change
|
||||
export type SecretCommitChange = BaseCommitChangeInfo & {
|
||||
resourceType: "secret";
|
||||
secretKey: string;
|
||||
changeType: string;
|
||||
secretVersionId?: string | null;
|
||||
secretVersion: string;
|
||||
secretId: string;
|
||||
versions?: {
|
||||
secretKey: string;
|
||||
secretComment: string;
|
||||
skipMultilineEncoding?: boolean | null;
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
metadata?: unknown;
|
||||
tags?: string[] | null;
|
||||
secretReminderRecipients?: string[] | null;
|
||||
secretValue: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
// Folder-specific change
|
||||
export type FolderCommitChange = BaseCommitChangeInfo & {
|
||||
resourceType: "folder";
|
||||
folderName: string;
|
||||
folderVersion: string;
|
||||
folderChangeId: string;
|
||||
versions?: {
|
||||
version: string;
|
||||
name?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
// Discriminated union
|
||||
export type CommitChangeWithCommitInfo = SecretCommitChange | FolderCommitChange;
|
||||
|
||||
// Type guards
|
||||
export const isSecretCommitChange = (change: CommitChangeWithCommitInfo): change is SecretCommitChange =>
|
||||
change.resourceType === "secret";
|
||||
|
||||
export const isFolderCommitChange = (change: CommitChangeWithCommitInfo): change is FolderCommitChange =>
|
||||
change.resourceType === "folder";
|
||||
|
||||
export const folderCommitChangesDALFactory = (db: TDbClient) => {
|
||||
const folderCommitChangesOrm = ormify(db, TableName.FolderCommitChanges);
|
||||
|
||||
const findByCommitId = async (
|
||||
folderCommitId: string,
|
||||
projectId: string,
|
||||
tx?: Knex
|
||||
): Promise<CommitChangeWithCommitInfo[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<TFolderCommitChanges>(TableName.FolderCommitChanges)
|
||||
.where(buildFindFilter({ folderCommitId }, TableName.FolderCommitChanges))
|
||||
.leftJoin<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderCommitChanges}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
.leftJoin<TSecretVersionsV2>(
|
||||
TableName.SecretVersionV2,
|
||||
`${TableName.FolderCommitChanges}.secretVersionId`,
|
||||
`${TableName.SecretVersionV2}.id`
|
||||
)
|
||||
.leftJoin<TSecretFolderVersions>(
|
||||
TableName.SecretFolderVersion,
|
||||
`${TableName.FolderCommitChanges}.folderVersionId`,
|
||||
`${TableName.SecretFolderVersion}.id`
|
||||
)
|
||||
.leftJoin<TProjectEnvironments>(
|
||||
TableName.Environment,
|
||||
`${TableName.FolderCommit}.envId`,
|
||||
`${TableName.Environment}.id`
|
||||
)
|
||||
.where((qb) => {
|
||||
if (projectId) {
|
||||
void qb.where(`${TableName.Environment}.projectId`, "=", projectId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommitChanges))
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderName"),
|
||||
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("folderChangeId"),
|
||||
db.ref("version").withSchema(TableName.SecretFolderVersion).as("folderVersion"),
|
||||
db.ref("key").withSchema(TableName.SecretVersionV2).as("secretKey"),
|
||||
db.ref("version").withSchema(TableName.SecretVersionV2).as("secretVersion"),
|
||||
db.ref("secretId").withSchema(TableName.SecretVersionV2),
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit)
|
||||
);
|
||||
|
||||
return docs.map((doc) => {
|
||||
// Determine if this is a secret or folder change based on populated fields
|
||||
if (doc.secretKey && doc.secretVersion && doc.secretId) {
|
||||
return {
|
||||
...doc,
|
||||
resourceType: "secret",
|
||||
secretKey: doc.secretKey,
|
||||
secretVersion: doc.secretVersion.toString(),
|
||||
secretId: doc.secretId
|
||||
} as SecretCommitChange;
|
||||
}
|
||||
return {
|
||||
...doc,
|
||||
resourceType: "folder",
|
||||
folderName: doc.folderName,
|
||||
folderVersion: doc.folderVersion.toString(),
|
||||
folderChangeId: doc.folderChangeId
|
||||
} as FolderCommitChange;
|
||||
});
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByCommitId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findBySecretVersionId = async (secretVersionId: string, tx?: Knex): Promise<SecretCommitChange[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<
|
||||
TFolderCommitChanges &
|
||||
Pick<TFolderCommits, "actorMetadata" | "actorType" | "message" | "createdAt" | "folderId">
|
||||
>(TableName.FolderCommitChanges)
|
||||
.where(buildFindFilter({ secretVersionId }, TableName.FolderCommitChanges))
|
||||
.select(selectAllTableCols(TableName.FolderCommitChanges))
|
||||
.join(TableName.FolderCommit, `${TableName.FolderCommitChanges}.folderCommitId`, `${TableName.FolderCommit}.id`)
|
||||
.leftJoin<TSecretVersionsV2>(
|
||||
TableName.SecretVersionV2,
|
||||
`${TableName.FolderCommitChanges}.secretVersionId`,
|
||||
`${TableName.SecretVersionV2}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit),
|
||||
db.ref("key").withSchema(TableName.SecretVersionV2).as("secretKey"),
|
||||
db.ref("version").withSchema(TableName.SecretVersionV2).as("secretVersion"),
|
||||
db.ref("secretId").withSchema(TableName.SecretVersionV2)
|
||||
);
|
||||
|
||||
return docs
|
||||
.filter((doc) => doc.secretKey && doc.secretVersion && doc.secretId)
|
||||
.map(
|
||||
(doc): SecretCommitChange => ({
|
||||
...doc,
|
||||
resourceType: "secret",
|
||||
secretKey: doc.secretKey,
|
||||
secretVersion: doc.secretVersion.toString(),
|
||||
secretId: doc.secretId
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindBySecretVersionId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByFolderVersionId = async (folderVersionId: string, tx?: Knex): Promise<FolderCommitChange[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<
|
||||
TFolderCommitChanges &
|
||||
Pick<TFolderCommits, "actorMetadata" | "actorType" | "message" | "createdAt" | "folderId">
|
||||
>(TableName.FolderCommitChanges)
|
||||
.where(buildFindFilter({ folderVersionId }, TableName.FolderCommitChanges))
|
||||
.select(selectAllTableCols(TableName.FolderCommitChanges))
|
||||
.join(TableName.FolderCommit, `${TableName.FolderCommitChanges}.folderCommitId`, `${TableName.FolderCommit}.id`)
|
||||
.leftJoin<TSecretFolderVersions>(
|
||||
TableName.SecretFolderVersion,
|
||||
`${TableName.FolderCommitChanges}.folderVersionId`,
|
||||
`${TableName.SecretFolderVersion}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("actorMetadata").withSchema(TableName.FolderCommit),
|
||||
db.ref("actorType").withSchema(TableName.FolderCommit),
|
||||
db.ref("message").withSchema(TableName.FolderCommit),
|
||||
db.ref("createdAt").withSchema(TableName.FolderCommit),
|
||||
db.ref("folderId").withSchema(TableName.FolderCommit),
|
||||
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderName"),
|
||||
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("folderChangeId"),
|
||||
db.ref("version").withSchema(TableName.SecretFolderVersion).as("folderVersion")
|
||||
);
|
||||
|
||||
return docs
|
||||
.filter((doc) => doc.folderName && doc.folderVersion && doc.folderChangeId)
|
||||
.map(
|
||||
(doc): FolderCommitChange => ({
|
||||
...doc,
|
||||
resourceType: "folder",
|
||||
folderName: doc.folderName,
|
||||
folderVersion: doc.folderVersion!.toString(),
|
||||
folderChangeId: doc.folderChangeId
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByFolderVersionId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...folderCommitChangesOrm,
|
||||
findByCommitId,
|
||||
findBySecretVersionId,
|
||||
findByFolderVersionId
|
||||
};
|
||||
};
|
||||
@@ -1,513 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TFolderCommitChanges,
|
||||
TFolderCommits,
|
||||
TProjectEnvironments,
|
||||
TSecretFolderVersions,
|
||||
TSecretVersionsV2
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderCommitDALFactory = ReturnType<typeof folderCommitDALFactory>;
|
||||
|
||||
export const folderCommitDALFactory = (db: TDbClient) => {
|
||||
const folderCommitOrm = ormify(db, TableName.FolderCommit);
|
||||
const { delete: deleteOp, deleteById, ...restOfOrm } = folderCommitOrm;
|
||||
|
||||
const findByFolderId = async (folderId: string, tx?: Knex): Promise<TFolderCommits[]> => {
|
||||
try {
|
||||
const trx = tx || db.replicaNode();
|
||||
|
||||
// First, get all folder commits
|
||||
const folderCommits = await trx(TableName.FolderCommit)
|
||||
.where({ folderId })
|
||||
.select("*")
|
||||
.orderBy("createdAt", "desc");
|
||||
|
||||
if (folderCommits.length === 0) return [];
|
||||
|
||||
// Get all commit IDs
|
||||
const commitIds = folderCommits.map((commit) => commit.id);
|
||||
|
||||
// Then get all related changes
|
||||
const changes = await trx(TableName.FolderCommitChanges).whereIn("folderCommitId", commitIds).select("*");
|
||||
|
||||
const changesMap = changes.reduce(
|
||||
(acc, change) => {
|
||||
const { folderCommitId } = change;
|
||||
if (!acc[folderCommitId]) acc[folderCommitId] = [];
|
||||
acc[folderCommitId].push(change);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, TFolderCommitChanges[]>
|
||||
);
|
||||
|
||||
return folderCommits.map((commit) => ({
|
||||
...commit,
|
||||
changes: changesMap[commit.id] || []
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByFolderId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestCommit = async (
|
||||
folderId: string,
|
||||
projectId?: string,
|
||||
tx?: Knex
|
||||
): Promise<TFolderCommits | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ folderId })
|
||||
.leftJoin(TableName.Environment, `${TableName.FolderCommit}.envId`, `${TableName.Environment}.id`)
|
||||
.where((qb) => {
|
||||
if (projectId) {
|
||||
void qb.where(`${TableName.Environment}.projectId`, "=", projectId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommit" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestCommitByFolderIds = async (folderIds: string[], tx?: Knex): Promise<TFolderCommits[] | undefined> => {
|
||||
try {
|
||||
// First get max commitId for each folderId
|
||||
const maxCommitIdSubquery = (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.select("folderId")
|
||||
.max("commitId as maxCommitId")
|
||||
.whereIn("folderId", folderIds)
|
||||
.groupBy("folderId");
|
||||
|
||||
// Join with main table to get complete records for each max commitId
|
||||
const docs = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
// eslint-disable-next-line func-names
|
||||
.join<TFolderCommits>(maxCommitIdSubquery.as("latest"), function () {
|
||||
this.on(`${TableName.FolderCommit}.folderId`, "=", "latest.folderId").andOn(
|
||||
`${TableName.FolderCommit}.commitId`,
|
||||
"=",
|
||||
"latest.maxCommitId"
|
||||
);
|
||||
});
|
||||
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommitByFolderIds" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestEnvCommit = async (envId: string, tx?: Knex): Promise<TFolderCommits | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where(`${TableName.FolderCommit}.envId`, "=", envId)
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommit" });
|
||||
}
|
||||
};
|
||||
|
||||
const findMultipleLatestCommits = async (folderIds: string[], tx?: Knex): Promise<TFolderCommits[]> => {
|
||||
try {
|
||||
const knexInstance = tx || db.replicaNode();
|
||||
|
||||
// Get the latest commitId for each folderId
|
||||
const subquery = knexInstance(TableName.FolderCommit)
|
||||
.whereIn("folderId", folderIds)
|
||||
.groupBy("folderId")
|
||||
.select("folderId")
|
||||
.max("commitId as maxCommitId");
|
||||
|
||||
// Then fetch the complete rows matching those latest commits
|
||||
const docs = await knexInstance(TableName.FolderCommit)
|
||||
// eslint-disable-next-line func-names
|
||||
.innerJoin<TFolderCommits>(subquery.as("latest"), function () {
|
||||
this.on(`${TableName.FolderCommit}.folderId`, "=", "latest.folderId").andOn(
|
||||
`${TableName.FolderCommit}.commitId`,
|
||||
"=",
|
||||
"latest.maxCommitId"
|
||||
);
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit));
|
||||
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindMultipleLatestCommits" });
|
||||
}
|
||||
};
|
||||
|
||||
const getNumberOfCommitsSince = async (folderId: string, folderCommitId: string, tx?: Knex): Promise<number> => {
|
||||
try {
|
||||
const referencedCommit = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ id: folderCommitId })
|
||||
.select("commitId")
|
||||
.first();
|
||||
|
||||
if (referencedCommit?.commitId) {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ folderId })
|
||||
.where("commitId", ">", referencedCommit.commitId)
|
||||
.count();
|
||||
return Number(doc?.[0].count);
|
||||
}
|
||||
return 0;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "getNumberOfCommitsSince" });
|
||||
}
|
||||
};
|
||||
|
||||
const getEnvNumberOfCommitsSince = async (envId: string, folderCommitId: string, tx?: Knex): Promise<number> => {
|
||||
try {
|
||||
const referencedCommit = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ id: folderCommitId })
|
||||
.select("commitId")
|
||||
.first();
|
||||
|
||||
if (referencedCommit?.commitId) {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where(`${TableName.FolderCommit}.envId`, "=", envId)
|
||||
.where("commitId", ">", referencedCommit.commitId)
|
||||
.count();
|
||||
return Number(doc?.[0].count);
|
||||
}
|
||||
return 0;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "getNumberOfCommitsSince" });
|
||||
}
|
||||
};
|
||||
|
||||
const findCommitsToRecreate = async (
|
||||
folderId: string,
|
||||
targetCommitNumber: bigint,
|
||||
checkpointCommitNumber: bigint,
|
||||
tx?: Knex
|
||||
): Promise<
|
||||
(TFolderCommits & {
|
||||
changes: (TFolderCommitChanges & {
|
||||
referencedSecretId?: string;
|
||||
referencedFolderId?: string;
|
||||
folderName?: string;
|
||||
folderVersion?: string;
|
||||
secretKey?: string;
|
||||
secretVersion?: string;
|
||||
})[];
|
||||
})[]
|
||||
> => {
|
||||
try {
|
||||
// First get all the commits in the range
|
||||
const commits = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderId }, TableName.FolderCommit))
|
||||
.andWhere(`${TableName.FolderCommit}.commitId`, ">", checkpointCommitNumber.toString())
|
||||
.andWhere(`${TableName.FolderCommit}.commitId`, "<=", targetCommitNumber.toString())
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy(`${TableName.FolderCommit}.commitId`, "asc");
|
||||
|
||||
// If no commits found, return empty array
|
||||
if (!commits.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all the commit IDs
|
||||
const commitIds = commits.map((commit) => commit.id);
|
||||
|
||||
// Get all changes for these commits in a single query
|
||||
const allChanges = await (tx || db.replicaNode())(TableName.FolderCommitChanges)
|
||||
.whereIn(`${TableName.FolderCommitChanges}.folderCommitId`, commitIds)
|
||||
.leftJoin<TSecretVersionsV2>(
|
||||
TableName.SecretVersionV2,
|
||||
`${TableName.FolderCommitChanges}.secretVersionId`,
|
||||
`${TableName.SecretVersionV2}.id`
|
||||
)
|
||||
.leftJoin<TSecretFolderVersions>(
|
||||
TableName.SecretFolderVersion,
|
||||
`${TableName.FolderCommitChanges}.folderVersionId`,
|
||||
`${TableName.SecretFolderVersion}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.FolderCommitChanges))
|
||||
.select(
|
||||
db.ref("secretId").withSchema(TableName.SecretVersionV2).as("referencedSecretId"),
|
||||
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("referencedFolderId"),
|
||||
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderName"),
|
||||
db.ref("version").withSchema(TableName.SecretFolderVersion).as("folderVersion"),
|
||||
db.ref("key").withSchema(TableName.SecretVersionV2).as("secretKey"),
|
||||
db.ref("version").withSchema(TableName.SecretVersionV2).as("secretVersion")
|
||||
);
|
||||
|
||||
// Organize changes by commit ID
|
||||
const changesByCommitId = allChanges.reduce(
|
||||
(acc, change) => {
|
||||
if (!acc[change.folderCommitId]) {
|
||||
acc[change.folderCommitId] = [];
|
||||
}
|
||||
acc[change.folderCommitId].push(change);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, TFolderCommitChanges[]>
|
||||
);
|
||||
|
||||
// Attach changes to each commit
|
||||
return commits.map((commit) => ({
|
||||
...commit,
|
||||
changes: changesByCommitId[commit.id] || []
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindCommitsToRecreate" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestCommitBetween = async ({
|
||||
folderId,
|
||||
startCommitId,
|
||||
endCommitId,
|
||||
tx
|
||||
}: {
|
||||
folderId: string;
|
||||
startCommitId?: string;
|
||||
endCommitId: string;
|
||||
tx?: Knex;
|
||||
}): Promise<TFolderCommits | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where("commitId", "<=", endCommitId)
|
||||
.where({ folderId })
|
||||
.where((qb) => {
|
||||
if (startCommitId) {
|
||||
void qb.where("commitId", ">=", startCommitId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommitBetween" });
|
||||
}
|
||||
};
|
||||
|
||||
const findAllCommitsBetween = async ({
|
||||
envId,
|
||||
startCommitId,
|
||||
endCommitId,
|
||||
tx
|
||||
}: {
|
||||
envId?: string;
|
||||
startCommitId?: string;
|
||||
endCommitId?: string;
|
||||
tx?: Knex;
|
||||
}): Promise<TFolderCommits[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where((qb) => {
|
||||
if (envId) {
|
||||
void qb.where(`${TableName.FolderCommit}.envId`, "=", envId);
|
||||
}
|
||||
if (startCommitId) {
|
||||
void qb.where("commitId", ">=", startCommitId);
|
||||
}
|
||||
if (endCommitId) {
|
||||
void qb.where("commitId", "<=", endCommitId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc");
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommitBetween" });
|
||||
}
|
||||
};
|
||||
|
||||
const findAllFolderCommitsAfter = async ({
|
||||
envId,
|
||||
startCommitId,
|
||||
tx
|
||||
}: {
|
||||
envId?: string;
|
||||
startCommitId?: string;
|
||||
tx?: Knex;
|
||||
}): Promise<TFolderCommits[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where((qb) => {
|
||||
if (envId) {
|
||||
void qb.where(`${TableName.FolderCommit}.envId`, "=", envId);
|
||||
}
|
||||
if (startCommitId) {
|
||||
void qb.where("commitId", ">=", startCommitId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc");
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestCommitBetween" });
|
||||
}
|
||||
};
|
||||
|
||||
const findPreviousCommitTo = async (
|
||||
folderId: string,
|
||||
commitId: string,
|
||||
tx?: Knex
|
||||
): Promise<TFolderCommits | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ folderId })
|
||||
.where("commitId", "<=", commitId)
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindPreviousCommitTo" });
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex, projectId?: string): Promise<TFolderCommits> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ id }, TableName.FolderCommit))
|
||||
.leftJoin<TProjectEnvironments>(
|
||||
TableName.Environment,
|
||||
`${TableName.FolderCommit}.envId`,
|
||||
`${TableName.Environment}.id`
|
||||
)
|
||||
.where((qb) => {
|
||||
if (projectId) {
|
||||
void qb.where(`${TableName.Environment}.projectId`, "=", projectId);
|
||||
}
|
||||
})
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
if (!doc) {
|
||||
throw new NotFoundError({
|
||||
message: `Folder commit not found for ID ${id}`
|
||||
});
|
||||
}
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindById" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByFolderIdPaginated = async (
|
||||
folderId: string,
|
||||
options: {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
sort?: "asc" | "desc";
|
||||
} = {},
|
||||
tx?: Knex
|
||||
): Promise<{
|
||||
commits: TFolderCommits[];
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
}> => {
|
||||
try {
|
||||
const { offset = 0, limit = 20, search, sort = "desc" } = options;
|
||||
const trx = tx || db.replicaNode();
|
||||
|
||||
// Build base query
|
||||
let baseQuery = trx(TableName.FolderCommit).where({ folderId });
|
||||
|
||||
// Add search functionality
|
||||
if (search) {
|
||||
baseQuery = baseQuery.where((qb) => {
|
||||
void qb.whereILike("message", `%${search}%`);
|
||||
});
|
||||
}
|
||||
|
||||
// Get total count
|
||||
const totalResult = await baseQuery.clone().count("*", { as: "count" }).first();
|
||||
const total = Number(totalResult?.count || 0);
|
||||
|
||||
// Get paginated commits
|
||||
const folderCommits = await baseQuery.select("*").orderBy("createdAt", sort).limit(limit).offset(offset);
|
||||
|
||||
if (folderCommits.length === 0) {
|
||||
return { commits: [], total, hasMore: false };
|
||||
}
|
||||
|
||||
// Get all commit IDs for changes
|
||||
const commitIds = folderCommits.map((commit) => commit.id);
|
||||
|
||||
// Get all related changes
|
||||
const changes = await trx(TableName.FolderCommitChanges).whereIn("folderCommitId", commitIds).select("*");
|
||||
|
||||
const changesMap = changes.reduce(
|
||||
(acc, change) => {
|
||||
const { folderCommitId } = change;
|
||||
if (!acc[folderCommitId]) acc[folderCommitId] = [];
|
||||
acc[folderCommitId].push(change);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, TFolderCommitChanges[]>
|
||||
);
|
||||
|
||||
const commitsWithChanges = folderCommits.map((commit) => ({
|
||||
...commit,
|
||||
changes: changesMap[commit.id] || []
|
||||
}));
|
||||
|
||||
const hasMore = offset + limit < total;
|
||||
|
||||
return {
|
||||
commits: commitsWithChanges,
|
||||
total,
|
||||
hasMore
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByFolderIdPaginated" });
|
||||
}
|
||||
};
|
||||
|
||||
const findCommitBefore = async (
|
||||
folderId: string,
|
||||
commitId: bigint,
|
||||
tx?: Knex
|
||||
): Promise<TFolderCommits | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.FolderCommit)
|
||||
.where({ folderId })
|
||||
.where("commitId", "<", commitId.toString())
|
||||
.select(selectAllTableCols(TableName.FolderCommit))
|
||||
.orderBy("commitId", "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindCommitBefore" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...restOfOrm,
|
||||
findByFolderId,
|
||||
findLatestCommit,
|
||||
getNumberOfCommitsSince,
|
||||
findCommitsToRecreate,
|
||||
findMultipleLatestCommits,
|
||||
findAllCommitsBetween,
|
||||
findLatestCommitBetween,
|
||||
findLatestEnvCommit,
|
||||
getEnvNumberOfCommitsSince,
|
||||
findLatestCommitByFolderIds,
|
||||
findAllFolderCommitsAfter,
|
||||
findPreviousCommitTo,
|
||||
findById,
|
||||
findByFolderIdPaginated,
|
||||
findCommitBefore
|
||||
};
|
||||
};
|
||||
@@ -1,282 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TSecretFolders } from "@app/db/schemas";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
import { TFolderTreeCheckpointDALFactory } from "../folder-tree-checkpoint/folder-tree-checkpoint-dal";
|
||||
import { TFolderTreeCheckpointResourcesDALFactory } from "../folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TFolderCommitDALFactory } from "./folder-commit-dal";
|
||||
|
||||
// Define types for job data
|
||||
type TCreateFolderTreeCheckpointDTO = {
|
||||
envId: string;
|
||||
failedToAcquireLockCount?: number;
|
||||
folderCommitId?: string;
|
||||
};
|
||||
|
||||
type TFolderCommitQueueServiceFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "getItem" | "deleteItem">;
|
||||
folderTreeCheckpointDAL: Pick<
|
||||
TFolderTreeCheckpointDALFactory,
|
||||
"create" | "findLatestByEnvId" | "findNearestCheckpoint"
|
||||
>;
|
||||
folderTreeCheckpointResourcesDAL: Pick<
|
||||
TFolderTreeCheckpointResourcesDALFactory,
|
||||
"insertMany" | "findByTreeCheckpointId"
|
||||
>;
|
||||
folderCommitDAL: Pick<
|
||||
TFolderCommitDALFactory,
|
||||
"findLatestEnvCommit" | "getEnvNumberOfCommitsSince" | "findMultipleLatestCommits" | "findById"
|
||||
>;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findByEnvId">;
|
||||
};
|
||||
|
||||
export type TFolderCommitQueueServiceFactory = ReturnType<typeof folderCommitQueueServiceFactory>;
|
||||
|
||||
export const folderCommitQueueServiceFactory = ({
|
||||
queueService,
|
||||
keyStore,
|
||||
folderTreeCheckpointDAL,
|
||||
folderTreeCheckpointResourcesDAL,
|
||||
folderCommitDAL,
|
||||
folderDAL
|
||||
}: TFolderCommitQueueServiceFactoryDep) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
// Helper function to calculate delay for requeuing
|
||||
const getRequeueDelay = (failureCount?: number) => {
|
||||
if (!failureCount) return 0;
|
||||
|
||||
const baseDelay = 5000;
|
||||
const maxDelay = 30000;
|
||||
|
||||
const delay = Math.min(baseDelay * 2 ** failureCount, maxDelay);
|
||||
const jitter = delay * (0.5 + Math.random() * 0.5);
|
||||
|
||||
return jitter;
|
||||
};
|
||||
|
||||
const scheduleTreeCheckpoint = async (payload: TCreateFolderTreeCheckpointDTO) => {
|
||||
const { envId, failedToAcquireLockCount = 0 } = payload;
|
||||
|
||||
// Create a unique jobId for each retry to prevent conflicts
|
||||
const jobId =
|
||||
failedToAcquireLockCount > 0 ? `${envId}-retry-${failedToAcquireLockCount}-${Date.now()}` : `${envId}`;
|
||||
|
||||
await queueService.queue(QueueName.FolderTreeCheckpoint, QueueJobs.CreateFolderTreeCheckpoint, payload, {
|
||||
jobId,
|
||||
delay: getRequeueDelay(failedToAcquireLockCount),
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 3000
|
||||
},
|
||||
removeOnFail: {
|
||||
count: 3
|
||||
},
|
||||
removeOnComplete: true
|
||||
});
|
||||
};
|
||||
|
||||
// Sort folders by hierarchy (copied from the source code)
|
||||
const sortFoldersByHierarchy = (folders: TSecretFolders[]) => {
|
||||
const childrenMap = new Map<string, TSecretFolders[]>();
|
||||
const allFolderIds = new Set<string>();
|
||||
|
||||
folders.forEach((folder) => {
|
||||
if (folder.id) allFolderIds.add(folder.id);
|
||||
});
|
||||
|
||||
folders.forEach((folder) => {
|
||||
if (folder.parentId) {
|
||||
const children = childrenMap.get(folder.parentId) || [];
|
||||
children.push(folder);
|
||||
childrenMap.set(folder.parentId, children);
|
||||
}
|
||||
});
|
||||
|
||||
const rootFolders = folders.filter((folder) => !folder.parentId || !allFolderIds.has(folder.parentId));
|
||||
|
||||
const result = [];
|
||||
let currentLevel = rootFolders;
|
||||
|
||||
while (currentLevel.length > 0) {
|
||||
result.push(...currentLevel);
|
||||
|
||||
const nextLevel = [];
|
||||
for (const folder of currentLevel) {
|
||||
if (folder.id) {
|
||||
const children = childrenMap.get(folder.id) || [];
|
||||
nextLevel.push(...children);
|
||||
}
|
||||
}
|
||||
|
||||
currentLevel = nextLevel;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const createFolderTreeCheckpoint = async (jobData: TCreateFolderTreeCheckpointDTO, tx?: Knex) => {
|
||||
const { envId, folderCommitId, failedToAcquireLockCount = 0 } = jobData;
|
||||
|
||||
logger.info(`Folder tree checkpoint creation started [envId=${envId}] [attempt=${failedToAcquireLockCount + 1}]`);
|
||||
|
||||
// First, try to clear any stale locks before attempting to acquire
|
||||
if (failedToAcquireLockCount > 1) {
|
||||
try {
|
||||
await keyStore.deleteItem(KeyStorePrefixes.FolderTreeCheckpoint(envId));
|
||||
logger.info(`Cleared potential stale lock for envId ${envId} before attempt ${failedToAcquireLockCount + 1}`);
|
||||
} catch (error) {
|
||||
// This is fine if it fails, we'll still try to acquire the lock
|
||||
logger.info(`No stale lock found for envId ${envId}`);
|
||||
}
|
||||
}
|
||||
|
||||
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>> | undefined;
|
||||
|
||||
try {
|
||||
// Attempt to acquire the lock with a shorter timeout for first attempts
|
||||
const timeout = failedToAcquireLockCount > 3 ? 60 * 1000 : 15 * 1000;
|
||||
|
||||
logger.info(`Attempting to acquire lock for envId=${envId} with timeout ${timeout}ms`);
|
||||
|
||||
lock = await keyStore.acquireLock([KeyStorePrefixes.FolderTreeCheckpoint(envId)], timeout);
|
||||
|
||||
logger.info(`Successfully acquired lock for envId=${envId}`);
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
`Failed to acquire lock for folder tree checkpoint [envId=${envId}] [attempt=${failedToAcquireLockCount + 1}]`
|
||||
);
|
||||
|
||||
// Requeue with incremented failure count if under max attempts
|
||||
if (failedToAcquireLockCount < 10) {
|
||||
// Force a delay between retries
|
||||
const nextRetryCount = failedToAcquireLockCount + 1;
|
||||
|
||||
logger.info(`Scheduling retry #${nextRetryCount} for folder tree checkpoint [envId=${envId}]`);
|
||||
|
||||
// Create a new job with incremented counter
|
||||
await scheduleTreeCheckpoint({
|
||||
envId,
|
||||
folderCommitId,
|
||||
failedToAcquireLockCount: nextRetryCount
|
||||
});
|
||||
} else {
|
||||
// Max retries reached
|
||||
logger.error(`Maximum lock acquisition attempts (10) reached for envId ${envId}. Giving up.`);
|
||||
// Try to force-clear the lock for next time
|
||||
try {
|
||||
await keyStore.deleteItem(KeyStorePrefixes.FolderTreeCheckpoint(envId));
|
||||
} catch (clearError) {
|
||||
logger.error(clearError, `Failed to clear lock after maximum retries for envId=${envId}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lock) {
|
||||
logger.error(`Lock is undefined after acquisition for envId=${envId}. This should never happen.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Processing tree checkpoint data for envId=${envId}`);
|
||||
|
||||
const latestTreeCheckpoint = await folderTreeCheckpointDAL.findLatestByEnvId(envId, tx);
|
||||
|
||||
let latestCommit;
|
||||
if (folderCommitId) {
|
||||
latestCommit = await folderCommitDAL.findById(folderCommitId, tx);
|
||||
} else {
|
||||
latestCommit = await folderCommitDAL.findLatestEnvCommit(envId, tx);
|
||||
}
|
||||
if (!latestCommit) {
|
||||
logger.info(`Latest commit ID not found for envId ${envId}`);
|
||||
return;
|
||||
}
|
||||
const latestCommitId = latestCommit.id;
|
||||
|
||||
if (latestTreeCheckpoint) {
|
||||
const commitsSinceLastCheckpoint = await folderCommitDAL.getEnvNumberOfCommitsSince(
|
||||
envId,
|
||||
latestTreeCheckpoint.folderCommitId,
|
||||
tx
|
||||
);
|
||||
if (commitsSinceLastCheckpoint < Number(appCfg.PIT_TREE_CHECKPOINT_WINDOW)) {
|
||||
logger.info(
|
||||
`Commits since last checkpoint ${commitsSinceLastCheckpoint} is less than ${appCfg.PIT_TREE_CHECKPOINT_WINDOW}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const folders = await folderDAL.findByEnvId(envId, tx);
|
||||
const sortedFolders = sortFoldersByHierarchy(folders);
|
||||
const filteredFoldersIds = sortedFolders.filter((folder) => !folder.isReserved).map((folder) => folder.id);
|
||||
|
||||
const folderCommits = await folderCommitDAL.findMultipleLatestCommits(filteredFoldersIds, tx);
|
||||
const folderTreeCheckpoint = await folderTreeCheckpointDAL.create(
|
||||
{
|
||||
folderCommitId: latestCommitId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await folderTreeCheckpointResourcesDAL.insertMany(
|
||||
folderCommits.map((folderCommit) => ({
|
||||
folderTreeCheckpointId: folderTreeCheckpoint.id,
|
||||
folderId: folderCommit.folderId,
|
||||
folderCommitId: folderCommit.id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
||||
logger.info(`Folder tree checkpoint created successfully: ${folderTreeCheckpoint.id}`);
|
||||
} catch (error) {
|
||||
logger.error(error, `Error processing folder tree checkpoint [envId=${envId}]`);
|
||||
throw error;
|
||||
} finally {
|
||||
// Always release the lock
|
||||
try {
|
||||
if (lock) {
|
||||
await lock.release();
|
||||
logger.info(`Released lock for folder tree checkpoint [envId=${envId}]`);
|
||||
} else {
|
||||
logger.error(`No lock to release for envId=${envId}. This should never happen.`);
|
||||
}
|
||||
} catch (releaseError) {
|
||||
logger.error(releaseError, `Error releasing lock for folder tree checkpoint [envId=${envId}]`);
|
||||
// Try to force delete the lock if release fails
|
||||
try {
|
||||
await keyStore.deleteItem(KeyStorePrefixes.FolderTreeCheckpoint(envId));
|
||||
logger.info(`Force deleted lock after release failure for envId=${envId}`);
|
||||
} catch (deleteError) {
|
||||
logger.error(deleteError, `Failed to force delete lock after release failure for envId=${envId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
queueService.start(QueueName.FolderTreeCheckpoint, async (job) => {
|
||||
try {
|
||||
if (job.name === QueueJobs.CreateFolderTreeCheckpoint) {
|
||||
const jobData = job.data as TCreateFolderTreeCheckpointDTO;
|
||||
await createFolderTreeCheckpoint(jobData);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error, "Error creating folder tree checkpoint:");
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
scheduleTreeCheckpoint: (envId: string) => scheduleTreeCheckpoint({ envId }),
|
||||
createFolderTreeCheckpoint: (envId: string, folderCommitId?: string, tx?: Knex) =>
|
||||
createFolderTreeCheckpoint({ envId, folderCommitId }, tx)
|
||||
};
|
||||
};
|
||||
@@ -1,143 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Base schema shared by both secret and folder changes
|
||||
const baseChangeSchema = z.object({
|
||||
id: z.string(),
|
||||
folderCommitId: z.string(),
|
||||
changeType: z.string(),
|
||||
isUpdate: z.boolean().optional(),
|
||||
createdAt: z.union([z.string(), z.date()]),
|
||||
updatedAt: z.union([z.string(), z.date()]),
|
||||
actorMetadata: z
|
||||
.union([
|
||||
z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().optional()
|
||||
}),
|
||||
z.unknown()
|
||||
])
|
||||
.optional(),
|
||||
actorType: z.string(),
|
||||
message: z.string().nullable().optional(),
|
||||
folderId: z.string()
|
||||
});
|
||||
|
||||
// Secret-specific versions schema
|
||||
const secretVersionSchema = z.object({
|
||||
secretKey: z.string(),
|
||||
secretComment: z.string(),
|
||||
skipMultilineEncoding: z.boolean().nullable().optional(),
|
||||
tags: z.array(z.string()).nullable().optional(),
|
||||
metadata: z.unknown().nullable().optional(),
|
||||
secretValue: z.string()
|
||||
});
|
||||
|
||||
// Folder-specific versions schema
|
||||
const folderVersionSchema = z.object({
|
||||
version: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional().nullable()
|
||||
});
|
||||
|
||||
// Secret commit change schema
|
||||
const secretCommitChangeSchema = baseChangeSchema.extend({
|
||||
resourceType: z.literal("secret"),
|
||||
secretVersionId: z.string().optional().nullable(),
|
||||
secretKey: z.string(),
|
||||
secretVersion: z.union([z.string(), z.number()]),
|
||||
secretId: z.string(),
|
||||
versions: z.array(secretVersionSchema).optional()
|
||||
});
|
||||
|
||||
// Folder commit change schema
|
||||
const folderCommitChangeSchema = baseChangeSchema.extend({
|
||||
resourceType: z.literal("folder"),
|
||||
folderVersionId: z.string().optional().nullable(),
|
||||
folderName: z.string(),
|
||||
folderChangeId: z.string(),
|
||||
folderVersion: z.union([z.string(), z.number()]),
|
||||
versions: z.array(folderVersionSchema).optional()
|
||||
});
|
||||
|
||||
// Discriminated union for commit changes
|
||||
export const commitChangeSchema = z.discriminatedUnion("resourceType", [
|
||||
secretCommitChangeSchema,
|
||||
folderCommitChangeSchema
|
||||
]);
|
||||
|
||||
// Commit schema
|
||||
const commitSchema = z.object({
|
||||
id: z.string(),
|
||||
commitId: z.string(),
|
||||
actorMetadata: z
|
||||
.union([
|
||||
z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().optional()
|
||||
}),
|
||||
z.unknown()
|
||||
])
|
||||
.optional(),
|
||||
actorType: z.string(),
|
||||
message: z.string().nullable().optional(),
|
||||
folderId: z.string(),
|
||||
envId: z.string(),
|
||||
createdAt: z.union([z.string(), z.date()]),
|
||||
updatedAt: z.union([z.string(), z.date()]),
|
||||
isLatest: z.boolean().default(false),
|
||||
changes: z.array(commitChangeSchema).optional()
|
||||
});
|
||||
|
||||
// Response schema
|
||||
export const commitChangesResponseSchema = z.object({
|
||||
changes: commitSchema
|
||||
});
|
||||
|
||||
// Base resource change schema for comparison results
|
||||
const baseResourceChangeSchema = z.object({
|
||||
id: z.string(),
|
||||
versionId: z.string(),
|
||||
oldVersionId: z.string().optional(),
|
||||
changeType: z.enum(["add", "delete", "update", "create"]),
|
||||
commitId: z.union([z.string(), z.bigint()]),
|
||||
createdAt: z.union([z.string(), z.date()]).optional(),
|
||||
parentId: z.string().optional(),
|
||||
isUpdate: z.boolean().optional(),
|
||||
fromVersion: z.union([z.string(), z.number()]).optional()
|
||||
});
|
||||
|
||||
// Secret resource change schema
|
||||
const secretResourceChangeSchema = baseResourceChangeSchema.extend({
|
||||
type: z.literal("secret"),
|
||||
secretKey: z.string(),
|
||||
secretVersion: z.union([z.string(), z.number()]),
|
||||
secretId: z.string(),
|
||||
versions: z
|
||||
.array(
|
||||
z.object({
|
||||
secretKey: z.string().optional(),
|
||||
secretComment: z.string().optional(),
|
||||
skipMultilineEncoding: z.boolean().nullable().optional(),
|
||||
secretReminderRepeatDays: z.number().nullable().optional(),
|
||||
tags: z.array(z.string()).nullable().optional(),
|
||||
metadata: z.unknown().nullable().optional(),
|
||||
secretReminderNote: z.string().nullable().optional(),
|
||||
secretValue: z.string().optional()
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
});
|
||||
|
||||
// Folder resource change schema
|
||||
const folderResourceChangeSchema = baseResourceChangeSchema.extend({
|
||||
type: z.literal("folder"),
|
||||
folderName: z.string(),
|
||||
folderVersion: z.union([z.string(), z.number()]),
|
||||
versions: z.array(folderVersionSchema).optional()
|
||||
});
|
||||
|
||||
// Discriminated union for resource changes
|
||||
export const resourceChangeSchema = z.discriminatedUnion("type", [
|
||||
secretResourceChangeSchema,
|
||||
folderResourceChangeSchema
|
||||
]);
|
||||
@@ -1,671 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/return-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { Knex } from "knex";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ProjectType, TSecretFolderVersions, TSecretVersionsV2 } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import {
|
||||
ChangeType,
|
||||
CommitType,
|
||||
folderCommitServiceFactory,
|
||||
ResourceChange,
|
||||
TFolderCommitServiceFactory
|
||||
} from "./folder-commit-service";
|
||||
|
||||
// Mock config
|
||||
vi.mock("@app/lib/config/env", () => ({
|
||||
getConfig: () => ({
|
||||
PIT_CHECKPOINT_WINDOW: 5,
|
||||
PIT_TREE_CHECKPOINT_WINDOW: 10
|
||||
})
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
vi.mock("@app/lib/logger", () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
describe("folderCommitServiceFactory", () => {
|
||||
// Properly type the mock functions
|
||||
type TransactionCallback<T> = (trx: Knex) => Promise<T>;
|
||||
|
||||
// Mock dependencies
|
||||
const mockFolderCommitDAL = {
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
findById: vi.fn().mockResolvedValue({}),
|
||||
findByFolderId: vi.fn().mockResolvedValue([]),
|
||||
findLatestCommit: vi.fn().mockResolvedValue({}),
|
||||
transaction: vi.fn().mockImplementation(<T>(callback: TransactionCallback<T>) => callback({} as Knex)),
|
||||
getNumberOfCommitsSince: vi.fn().mockResolvedValue(0),
|
||||
getEnvNumberOfCommitsSince: vi.fn().mockResolvedValue(0),
|
||||
findCommitsToRecreate: vi.fn().mockResolvedValue([]),
|
||||
findMultipleLatestCommits: vi.fn().mockResolvedValue([]),
|
||||
findLatestCommitBetween: vi.fn().mockResolvedValue({}),
|
||||
findAllCommitsBetween: vi.fn().mockResolvedValue([]),
|
||||
findLatestEnvCommit: vi.fn().mockResolvedValue({}),
|
||||
findLatestCommitByFolderIds: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockKmsService = {
|
||||
createCipherPairWithDataKey: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockFolderCommitChangesDAL = {
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
findByCommitId: vi.fn().mockResolvedValue([]),
|
||||
insertMany: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
const mockFolderCheckpointDAL = {
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
findByFolderId: vi.fn().mockResolvedValue([]),
|
||||
findLatestByFolderId: vi.fn().mockResolvedValue(null),
|
||||
findNearestCheckpoint: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockFolderCheckpointResourcesDAL = {
|
||||
insertMany: vi.fn().mockResolvedValue([]),
|
||||
findByCheckpointId: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
const mockFolderTreeCheckpointDAL = {
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
findByProjectId: vi.fn().mockResolvedValue([]),
|
||||
findLatestByProjectId: vi.fn().mockResolvedValue({}),
|
||||
findNearestCheckpoint: vi.fn().mockResolvedValue({}),
|
||||
findLatestByEnvId: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockFolderTreeCheckpointResourcesDAL = {
|
||||
insertMany: vi.fn().mockResolvedValue([]),
|
||||
findByTreeCheckpointId: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
const mockUserDAL = {
|
||||
findById: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockIdentityDAL = {
|
||||
findById: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockFolderDAL = {
|
||||
findByParentId: vi.fn().mockResolvedValue([]),
|
||||
findByProjectId: vi.fn().mockResolvedValue([]),
|
||||
deleteById: vi.fn().mockResolvedValue({}),
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
updateById: vi.fn().mockResolvedValue({}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
find: vi.fn().mockResolvedValue([]),
|
||||
findById: vi.fn().mockResolvedValue({}),
|
||||
findByEnvId: vi.fn().mockResolvedValue([]),
|
||||
findFoldersByRootAndIds: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
const mockFolderVersionDAL = {
|
||||
findLatestFolderVersions: vi.fn().mockResolvedValue({}),
|
||||
findById: vi.fn().mockResolvedValue({}),
|
||||
deleteById: vi.fn().mockResolvedValue({}),
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
updateById: vi.fn().mockResolvedValue({}),
|
||||
find: vi.fn().mockResolvedValue({}), // Changed from [] to {} to match Object.values() expectation
|
||||
findByIdsWithLatestVersion: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockSecretVersionV2BridgeDAL = {
|
||||
findLatestVersionByFolderId: vi.fn().mockResolvedValue([]),
|
||||
findById: vi.fn().mockResolvedValue({}),
|
||||
deleteById: vi.fn().mockResolvedValue({}),
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
updateById: vi.fn().mockResolvedValue({}),
|
||||
find: vi.fn().mockResolvedValue([]),
|
||||
findByIdsWithLatestVersion: vi.fn().mockResolvedValue({}),
|
||||
findLatestVersionMany: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockSecretV2BridgeDAL = {
|
||||
deleteById: vi.fn().mockResolvedValue({}),
|
||||
create: vi.fn().mockResolvedValue({}),
|
||||
updateById: vi.fn().mockResolvedValue({}),
|
||||
update: vi.fn().mockResolvedValue({}),
|
||||
insertMany: vi.fn().mockResolvedValue([]),
|
||||
invalidateSecretCacheByProjectId: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockProjectDAL = {
|
||||
findById: vi.fn().mockResolvedValue({}),
|
||||
findProjectByEnvId: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockFolderCommitQueueService = {
|
||||
scheduleTreeCheckpoint: vi.fn().mockResolvedValue({}),
|
||||
createFolderTreeCheckpoint: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockPermissionService = {
|
||||
getProjectPermission: vi.fn().mockResolvedValue({})
|
||||
};
|
||||
|
||||
const mockSecretTagDAL = {
|
||||
findSecretTagsByVersionId: vi.fn().mockResolvedValue([]),
|
||||
saveTagsToSecretV2: vi.fn().mockResolvedValue([]),
|
||||
findSecretTagsBySecretId: vi.fn().mockResolvedValue([]),
|
||||
deleteTagsToSecretV2: vi.fn().mockResolvedValue([]),
|
||||
saveTagsToSecretVersionV2: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
const mockResourceMetadataDAL = {
|
||||
find: vi.fn().mockResolvedValue([]),
|
||||
insertMany: vi.fn().mockResolvedValue([]),
|
||||
delete: vi.fn().mockResolvedValue([])
|
||||
};
|
||||
|
||||
let folderCommitService: TFolderCommitServiceFactory;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
folderCommitService = folderCommitServiceFactory({
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderCommitDAL: mockFolderCommitDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderCommitChangesDAL: mockFolderCommitChangesDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderCheckpointDAL: mockFolderCheckpointDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderCheckpointResourcesDAL: mockFolderCheckpointResourcesDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderTreeCheckpointDAL: mockFolderTreeCheckpointDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderTreeCheckpointResourcesDAL: mockFolderTreeCheckpointResourcesDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
userDAL: mockUserDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
identityDAL: mockIdentityDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderDAL: mockFolderDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
folderVersionDAL: mockFolderVersionDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
secretVersionV2BridgeDAL: mockSecretVersionV2BridgeDAL,
|
||||
projectDAL: mockProjectDAL,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
secretV2BridgeDAL: mockSecretV2BridgeDAL,
|
||||
folderCommitQueueService: mockFolderCommitQueueService,
|
||||
// @ts-expect-error - Mock implementation doesn't need all interface methods for testing
|
||||
permissionService: mockPermissionService,
|
||||
kmsService: mockKmsService,
|
||||
secretTagDAL: mockSecretTagDAL,
|
||||
resourceMetadataDAL: mockResourceMetadataDAL
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("createCommit", () => {
|
||||
it("should successfully create a commit with user actor", async () => {
|
||||
// Arrange
|
||||
const userData = { id: "user-id", username: "testuser" };
|
||||
const folderData = { id: "folder-id", envId: "env-id" };
|
||||
const commitData = { id: "commit-id", folderId: "folder-id" };
|
||||
|
||||
mockUserDAL.findById.mockResolvedValue(userData);
|
||||
mockFolderDAL.findById.mockResolvedValue(folderData);
|
||||
mockFolderCommitDAL.create.mockResolvedValue(commitData);
|
||||
mockFolderCheckpointDAL.findLatestByFolderId.mockResolvedValue(null);
|
||||
mockFolderCommitDAL.findLatestCommit.mockResolvedValue({ id: "latest-commit-id" });
|
||||
mockFolderDAL.findByParentId.mockResolvedValue([]);
|
||||
mockSecretVersionV2BridgeDAL.findLatestVersionByFolderId.mockResolvedValue([]);
|
||||
|
||||
const data = {
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: { id: userData.id }
|
||||
},
|
||||
message: "Test commit",
|
||||
folderId: folderData.id,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
secretVersionId: "secret-version-1"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.createCommit(data);
|
||||
|
||||
// Assert
|
||||
expect(mockUserDAL.findById).toHaveBeenCalledWith(userData.id, undefined);
|
||||
expect(mockFolderDAL.findById).toHaveBeenCalledWith(folderData.id, undefined);
|
||||
expect(mockFolderCommitDAL.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorType: ActorType.USER,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
actorMetadata: expect.objectContaining({ name: userData.username }),
|
||||
message: data.message,
|
||||
folderId: data.folderId,
|
||||
envId: folderData.envId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(mockFolderCommitChangesDAL.insertMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
folderCommitId: commitData.id,
|
||||
changeType: data.changes[0].type,
|
||||
secretVersionId: data.changes[0].secretVersionId
|
||||
})
|
||||
]),
|
||||
undefined
|
||||
);
|
||||
expect(mockFolderCommitQueueService.scheduleTreeCheckpoint).toHaveBeenCalledWith(folderData.envId);
|
||||
expect(result).toEqual(commitData);
|
||||
});
|
||||
|
||||
it("should successfully create a commit with identity actor", async () => {
|
||||
// Arrange
|
||||
const identityData = { id: "identity-id", name: "testidentity" };
|
||||
const folderData = { id: "folder-id", envId: "env-id" };
|
||||
const commitData = { id: "commit-id", folderId: "folder-id" };
|
||||
|
||||
mockIdentityDAL.findById.mockResolvedValue(identityData);
|
||||
mockFolderDAL.findById.mockResolvedValue(folderData);
|
||||
mockFolderCommitDAL.create.mockResolvedValue(commitData);
|
||||
mockFolderCheckpointDAL.findLatestByFolderId.mockResolvedValue(null);
|
||||
mockFolderCommitDAL.findLatestCommit.mockResolvedValue({ id: "latest-commit-id" });
|
||||
mockFolderDAL.findByParentId.mockResolvedValue([]);
|
||||
mockSecretVersionV2BridgeDAL.findLatestVersionByFolderId.mockResolvedValue([]);
|
||||
|
||||
// Mock folderVersionDAL.find to return an object with folder version data
|
||||
mockFolderVersionDAL.find.mockResolvedValue({
|
||||
"folder-version-1": {
|
||||
id: "folder-version-1",
|
||||
folderId: "sub-folder-id",
|
||||
envId: "env-id",
|
||||
name: "Test Folder",
|
||||
version: 1
|
||||
}
|
||||
});
|
||||
|
||||
const data = {
|
||||
actor: {
|
||||
type: ActorType.IDENTITY,
|
||||
metadata: { id: identityData.id }
|
||||
},
|
||||
message: "Test commit",
|
||||
folderId: folderData.id,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
folderVersionId: "folder-version-1"
|
||||
}
|
||||
],
|
||||
omitIgnoreFilter: true
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.createCommit(data);
|
||||
|
||||
// Assert
|
||||
expect(mockIdentityDAL.findById).toHaveBeenCalledWith(identityData.id, undefined);
|
||||
expect(mockFolderDAL.findById).toHaveBeenCalledWith(folderData.id, undefined);
|
||||
expect(mockFolderCommitDAL.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorType: ActorType.IDENTITY,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
actorMetadata: expect.objectContaining({ name: identityData.name }),
|
||||
message: data.message,
|
||||
folderId: data.folderId,
|
||||
envId: folderData.envId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(mockFolderCommitChangesDAL.insertMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
folderCommitId: commitData.id,
|
||||
changeType: data.changes[0].type,
|
||||
folderVersionId: data.changes[0].folderVersionId
|
||||
})
|
||||
]),
|
||||
undefined
|
||||
);
|
||||
expect(mockFolderCommitQueueService.scheduleTreeCheckpoint).toHaveBeenCalledWith(folderData.envId);
|
||||
expect(result).toEqual(commitData);
|
||||
});
|
||||
|
||||
it("should throw NotFoundError when folder does not exist", async () => {
|
||||
// Arrange
|
||||
mockFolderDAL.findById.mockResolvedValue(null);
|
||||
|
||||
const data = {
|
||||
actor: {
|
||||
type: ActorType.PLATFORM
|
||||
},
|
||||
message: "Test commit",
|
||||
folderId: "non-existent-folder",
|
||||
changes: []
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await expect(folderCommitService.createCommit(data)).rejects.toThrow(NotFoundError);
|
||||
expect(mockFolderDAL.findById).toHaveBeenCalledWith("non-existent-folder", undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addCommitChange", () => {
|
||||
it("should successfully add a change to an existing commit", async () => {
|
||||
// Arrange
|
||||
const commitData = { id: "commit-id", folderId: "folder-id" };
|
||||
const changeData = { id: "change-id", folderCommitId: "commit-id" };
|
||||
|
||||
mockFolderCommitDAL.findById.mockResolvedValue(commitData);
|
||||
mockFolderCommitChangesDAL.create.mockResolvedValue(changeData);
|
||||
|
||||
const data = {
|
||||
folderCommitId: commitData.id,
|
||||
changeType: CommitType.ADD,
|
||||
secretVersionId: "secret-version-1"
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.addCommitChange(data);
|
||||
|
||||
// Assert
|
||||
expect(mockFolderCommitDAL.findById).toHaveBeenCalledWith(commitData.id, undefined);
|
||||
expect(mockFolderCommitChangesDAL.create).toHaveBeenCalledWith(data, undefined);
|
||||
expect(result).toEqual(changeData);
|
||||
});
|
||||
|
||||
it("should throw BadRequestError when neither secretVersionId nor folderVersionId is provided", async () => {
|
||||
// Arrange
|
||||
const data = {
|
||||
folderCommitId: "commit-id",
|
||||
changeType: CommitType.ADD
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await expect(folderCommitService.addCommitChange(data)).rejects.toThrow(BadRequestError);
|
||||
});
|
||||
|
||||
it("should throw NotFoundError when commit does not exist", async () => {
|
||||
// Arrange
|
||||
mockFolderCommitDAL.findById.mockResolvedValue(null);
|
||||
|
||||
const data = {
|
||||
folderCommitId: "non-existent-commit",
|
||||
changeType: CommitType.ADD,
|
||||
secretVersionId: "secret-version-1"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await expect(folderCommitService.addCommitChange(data)).rejects.toThrow(NotFoundError);
|
||||
expect(mockFolderCommitDAL.findById).toHaveBeenCalledWith("non-existent-commit", undefined);
|
||||
});
|
||||
});
|
||||
|
||||
// Note: reconstructFolderState is an internal function not exposed in the public API
|
||||
// We'll test it indirectly through compareFolderStates
|
||||
|
||||
describe("compareFolderStates", () => {
|
||||
it("should mark all resources as creates when currentCommitId is not provided", async () => {
|
||||
// Arrange
|
||||
const targetCommitId = "target-commit-id";
|
||||
const targetCommit = { id: targetCommitId, commitId: 1, folderId: "folder-id" };
|
||||
|
||||
mockFolderCommitDAL.findById.mockResolvedValue(targetCommit);
|
||||
// Mock how compareFolderStates would process the results internally
|
||||
mockFolderCheckpointDAL.findNearestCheckpoint.mockResolvedValue({ id: "checkpoint-id", commitId: "hash-0" });
|
||||
mockFolderCheckpointResourcesDAL.findByCheckpointId.mockResolvedValue([
|
||||
{ secretVersionId: "secret-version-1", referencedSecretId: "secret-1" },
|
||||
{ folderVersionId: "folder-version-1", referencedFolderId: "folder-1" }
|
||||
]);
|
||||
mockFolderCommitDAL.findCommitsToRecreate.mockResolvedValue([]);
|
||||
mockProjectDAL.findProjectByEnvId.mockResolvedValue({
|
||||
id: "project-id",
|
||||
name: "test-project",
|
||||
type: ProjectType.SecretManager
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.compareFolderStates({
|
||||
targetCommitId
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockFolderCommitDAL.findById).toHaveBeenCalledWith(targetCommitId, undefined);
|
||||
|
||||
// Verify we get resources marked as create
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
changeType: "create",
|
||||
commitId: targetCommit.commitId
|
||||
})
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createFolderCheckpoint", () => {
|
||||
it("should successfully create a checkpoint when force is true", async () => {
|
||||
// Arrange
|
||||
const folderCommitId = "commit-id";
|
||||
const folderId = "folder-id";
|
||||
const checkpointData = { id: "checkpoint-id", folderCommitId };
|
||||
|
||||
mockFolderDAL.findByParentId.mockResolvedValue([{ id: "subfolder-id" }]);
|
||||
mockFolderVersionDAL.findLatestFolderVersions.mockResolvedValue({ "subfolder-id": { id: "folder-version-1" } });
|
||||
mockSecretVersionV2BridgeDAL.findLatestVersionByFolderId.mockResolvedValue([{ id: "secret-version-1" }]);
|
||||
mockFolderCheckpointDAL.create.mockResolvedValue(checkpointData);
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.createFolderCheckpoint({
|
||||
folderId,
|
||||
folderCommitId,
|
||||
force: true
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockFolderCheckpointDAL.create).toHaveBeenCalledWith({ folderCommitId }, undefined);
|
||||
expect(mockFolderCheckpointResourcesDAL.insertMany).toHaveBeenCalled();
|
||||
expect(result).toBe(folderCommitId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deepRollbackFolder", () => {
|
||||
it("should throw NotFoundError when commit doesn't exist", async () => {
|
||||
// Arrange
|
||||
const targetCommitId = "non-existent-commit";
|
||||
const envId = "env-id";
|
||||
const actorId = "user-id";
|
||||
const actorType = ActorType.USER;
|
||||
const projectId = "project-id";
|
||||
|
||||
// Mock the transaction to properly handle the error
|
||||
mockFolderCommitDAL.transaction.mockImplementation(async (callback) => {
|
||||
return await callback({} as Knex);
|
||||
});
|
||||
|
||||
// Mock findById to return null inside the transaction
|
||||
mockFolderCommitDAL.findById.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(
|
||||
folderCommitService.deepRollbackFolder(targetCommitId, envId, actorId, actorType, projectId)
|
||||
).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createFolderTreeCheckpoint", () => {
|
||||
it("should create a tree checkpoint when checkpoint window is exceeded", async () => {
|
||||
// Arrange
|
||||
const envId = "env-id";
|
||||
const folderCommitId = "commit-id";
|
||||
const latestCommit = { id: folderCommitId };
|
||||
const latestTreeCheckpoint = { id: "tree-checkpoint-id", folderCommitId: "old-commit-id" };
|
||||
const folders = [
|
||||
{ id: "folder-1", isReserved: false },
|
||||
{ id: "folder-2", isReserved: false },
|
||||
{ id: "folder-3", isReserved: true } // Reserved folders should be filtered out
|
||||
];
|
||||
const folderCommits = [
|
||||
{ folderId: "folder-1", id: "commit-1" },
|
||||
{ folderId: "folder-2", id: "commit-2" }
|
||||
];
|
||||
const treeCheckpoint = { id: "new-tree-checkpoint-id" };
|
||||
|
||||
mockFolderCommitDAL.findLatestEnvCommit.mockResolvedValue(latestCommit);
|
||||
mockFolderTreeCheckpointDAL.findLatestByEnvId.mockResolvedValue(latestTreeCheckpoint);
|
||||
mockFolderCommitDAL.getEnvNumberOfCommitsSince.mockResolvedValue(15); // More than PIT_TREE_CHECKPOINT_WINDOW (10)
|
||||
mockFolderDAL.findByEnvId.mockResolvedValue(folders);
|
||||
mockFolderCommitDAL.findMultipleLatestCommits.mockResolvedValue(folderCommits);
|
||||
mockFolderTreeCheckpointDAL.create.mockResolvedValue(treeCheckpoint);
|
||||
|
||||
// Act
|
||||
await folderCommitService.createFolderTreeCheckpoint(envId);
|
||||
|
||||
// Assert
|
||||
expect(mockFolderCommitDAL.findLatestEnvCommit).toHaveBeenCalledWith(envId, undefined);
|
||||
expect(mockFolderTreeCheckpointDAL.create).toHaveBeenCalledWith({ folderCommitId }, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyFolderStateDifferences", () => {
|
||||
it("should process changes correctly", async () => {
|
||||
// Arrange
|
||||
const folderId = "folder-id";
|
||||
const projectId = "project-id";
|
||||
const actorId = "user-id";
|
||||
const actorType = ActorType.USER;
|
||||
|
||||
const differences = [
|
||||
{
|
||||
id: "secret-1",
|
||||
versionId: "v1",
|
||||
changeType: ChangeType.CREATE,
|
||||
commitId: BigInt(1)
|
||||
} as ResourceChange,
|
||||
{
|
||||
id: "folder-1",
|
||||
versionId: "v2",
|
||||
changeType: ChangeType.UPDATE,
|
||||
commitId: BigInt(1),
|
||||
folderName: "Test Folder",
|
||||
folderVersion: "v2"
|
||||
} as ResourceChange
|
||||
];
|
||||
|
||||
const secretVersions = {
|
||||
"secret-1": {
|
||||
id: "secret-version-1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
type: "shared",
|
||||
folderId: "folder-1",
|
||||
secretId: "secret-1",
|
||||
version: 1,
|
||||
key: "SECRET_KEY",
|
||||
encryptedValue: Buffer.from("encrypted"),
|
||||
encryptedComment: Buffer.from("comment"),
|
||||
skipMultilineEncoding: false,
|
||||
userId: "user-1",
|
||||
envId: "env-1",
|
||||
metadata: {}
|
||||
} as TSecretVersionsV2
|
||||
};
|
||||
|
||||
const folderVersions = {
|
||||
"folder-1": {
|
||||
folderId: "folder-1",
|
||||
version: 1,
|
||||
name: "Test Folder",
|
||||
envId: "env-1"
|
||||
} as TSecretFolderVersions
|
||||
};
|
||||
|
||||
// Mock folder lookup for the folder being processed
|
||||
mockFolderDAL.findById.mockImplementation((id) => {
|
||||
if (id === folderId) {
|
||||
return Promise.resolve({ id: folderId, envId: "env-1" });
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
// Mock latest commit lookup
|
||||
mockFolderCommitDAL.findLatestCommit.mockImplementation((id) => {
|
||||
if (id === folderId) {
|
||||
return Promise.resolve({ id: "latest-commit-id", folderId });
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
// Make sure findByParentId returns an array, not undefined
|
||||
mockFolderDAL.findByParentId.mockResolvedValue([]);
|
||||
|
||||
// Make sure other required functions return appropriate values
|
||||
mockFolderCheckpointDAL.findLatestByFolderId.mockResolvedValue(null);
|
||||
mockSecretVersionV2BridgeDAL.findLatestVersionByFolderId.mockResolvedValue([]);
|
||||
|
||||
// These mocks need to return objects with an id field
|
||||
mockSecretVersionV2BridgeDAL.findByIdsWithLatestVersion.mockResolvedValue(Object.values(secretVersions));
|
||||
mockFolderVersionDAL.findByIdsWithLatestVersion.mockResolvedValue(Object.values(folderVersions));
|
||||
mockSecretV2BridgeDAL.insertMany.mockResolvedValue([{ id: "new-secret-1" }]);
|
||||
mockSecretVersionV2BridgeDAL.create.mockResolvedValue({ id: "new-secret-version-1" });
|
||||
mockFolderDAL.updateById.mockResolvedValue({ id: "updated-folder-1" });
|
||||
mockFolderVersionDAL.create.mockResolvedValue({ id: "new-folder-version-1" });
|
||||
mockFolderCommitDAL.create.mockResolvedValue({ id: "new-commit-id" });
|
||||
mockSecretVersionV2BridgeDAL.findLatestVersionMany.mockResolvedValue([
|
||||
{
|
||||
id: "secret-version-1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
type: "shared",
|
||||
folderId: "folder-1",
|
||||
secretId: "secret-1",
|
||||
version: 1,
|
||||
key: "SECRET_KEY",
|
||||
encryptedValue: Buffer.from("encrypted"),
|
||||
encryptedComment: Buffer.from("comment"),
|
||||
skipMultilineEncoding: false,
|
||||
userId: "user-1",
|
||||
envId: "env-1",
|
||||
metadata: {}
|
||||
}
|
||||
]);
|
||||
|
||||
// Mock transaction
|
||||
mockFolderCommitDAL.transaction.mockImplementation(<T>(callback: TransactionCallback<T>) => callback({} as Knex));
|
||||
|
||||
// Act
|
||||
const result = await folderCommitService.applyFolderStateDifferences({
|
||||
differences,
|
||||
actorInfo: {
|
||||
actorType,
|
||||
actorId,
|
||||
message: "Applying changes"
|
||||
},
|
||||
folderId,
|
||||
projectId,
|
||||
reconstructNewFolders: false
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockFolderCommitDAL.create).toHaveBeenCalled();
|
||||
expect(mockSecretV2BridgeDAL.invalidateSecretCacheByProjectId).toHaveBeenCalledWith(projectId);
|
||||
|
||||
// Check that we got the right counts
|
||||
expect(result.totalChanges).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
-44
@@ -1,44 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TFolderTreeCheckpointResources } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderTreeCheckpointResourcesDALFactory = ReturnType<typeof folderTreeCheckpointResourcesDALFactory>;
|
||||
|
||||
type TFolderTreeCheckpointResourcesWithCommitId = TFolderTreeCheckpointResources & {
|
||||
commitId: bigint;
|
||||
};
|
||||
|
||||
export const folderTreeCheckpointResourcesDALFactory = (db: TDbClient) => {
|
||||
const folderTreeCheckpointResourcesOrm = ormify(db, TableName.FolderTreeCheckpointResources);
|
||||
|
||||
const findByTreeCheckpointId = async (
|
||||
folderTreeCheckpointId: string,
|
||||
tx?: Knex
|
||||
): Promise<TFolderTreeCheckpointResourcesWithCommitId[]> => {
|
||||
try {
|
||||
const docs = await (tx || db.replicaNode())<TFolderTreeCheckpointResources>(
|
||||
TableName.FolderTreeCheckpointResources
|
||||
)
|
||||
.join(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderTreeCheckpointResources}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderTreeCheckpointId }, TableName.FolderTreeCheckpointResources))
|
||||
.select(selectAllTableCols(TableName.FolderTreeCheckpointResources))
|
||||
.select(db.ref("commitId").withSchema(TableName.FolderCommit).as("commitId"));
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByTreeCheckpointId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...folderTreeCheckpointResourcesOrm,
|
||||
findByTreeCheckpointId
|
||||
};
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TFolderCommits, TFolderTreeCheckpoints } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TFolderTreeCheckpointDALFactory = ReturnType<typeof folderTreeCheckpointDALFactory>;
|
||||
|
||||
type TreeCheckpointWithCommitInfo = TFolderTreeCheckpoints & {
|
||||
commitId: bigint;
|
||||
};
|
||||
|
||||
export const folderTreeCheckpointDALFactory = (db: TDbClient) => {
|
||||
const folderTreeCheckpointOrm = ormify(db, TableName.FolderTreeCheckpoint);
|
||||
|
||||
const findByCommitId = async (folderCommitId: string, tx?: Knex): Promise<TFolderTreeCheckpoints | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())<TFolderTreeCheckpoints>(TableName.FolderTreeCheckpoint)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ folderCommitId }, TableName.FolderTreeCheckpoint))
|
||||
.select(selectAllTableCols(TableName.FolderTreeCheckpoint))
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByCommitId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findNearestCheckpoint = async (
|
||||
folderCommitId: bigint,
|
||||
envId: string,
|
||||
tx?: Knex
|
||||
): Promise<TreeCheckpointWithCommitInfo | undefined> => {
|
||||
try {
|
||||
const nearestCheckpoint = await (tx || db.replicaNode())(TableName.FolderTreeCheckpoint)
|
||||
.join<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderTreeCheckpoint}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(`${TableName.FolderCommit}.envId`, "=", envId)
|
||||
.andWhere(`${TableName.FolderCommit}.commitId`, "<=", folderCommitId.toString())
|
||||
.select(selectAllTableCols(TableName.FolderTreeCheckpoint))
|
||||
.select(db.ref("commitId").withSchema(TableName.FolderCommit))
|
||||
.orderBy(`${TableName.FolderCommit}.commitId`, "desc")
|
||||
.first();
|
||||
|
||||
return nearestCheckpoint;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindNearestCheckpoint" });
|
||||
}
|
||||
};
|
||||
|
||||
const findLatestByEnvId = async (envId: string, tx?: Knex): Promise<TFolderTreeCheckpoints | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())<TFolderTreeCheckpoints>(TableName.FolderTreeCheckpoint)
|
||||
.join<TFolderCommits>(
|
||||
TableName.FolderCommit,
|
||||
`${TableName.FolderTreeCheckpoint}.folderCommitId`,
|
||||
`${TableName.FolderCommit}.id`
|
||||
)
|
||||
.where(`${TableName.FolderCommit}.envId`, "=", envId)
|
||||
.orderBy(`${TableName.FolderTreeCheckpoint}.createdAt`, "desc")
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestByEnvId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...folderTreeCheckpointOrm,
|
||||
findByCommitId,
|
||||
findNearestCheckpoint,
|
||||
findLatestByEnvId
|
||||
};
|
||||
};
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
TProjectsUpdate
|
||||
} from "@app/db/schemas";
|
||||
import { BadRequestError, DatabaseError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { Filter, ProjectFilterType, SearchProjectSortBy } from "./project-types";
|
||||
@@ -425,16 +425,6 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
return { docs, totalCount: Number(docs?.[0]?.count ?? 0) };
|
||||
};
|
||||
|
||||
const findProjectByEnvId = async (envId: string, tx?: Knex) => {
|
||||
const project = await (tx || db.replicaNode())(TableName.Project)
|
||||
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ id: envId }, TableName.Environment))
|
||||
.select(selectAllTableCols(TableName.Project))
|
||||
.first();
|
||||
return project;
|
||||
};
|
||||
|
||||
const countOfOrgProjects = async (orgId: string | null, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.Project)
|
||||
@@ -463,7 +453,6 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
checkProjectUpgradeStatus,
|
||||
getProjectFromSplitId,
|
||||
searchProjects,
|
||||
findProjectByEnvId,
|
||||
countOfOrgProjects
|
||||
};
|
||||
};
|
||||
|
||||
@@ -488,75 +488,6 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findFoldersByRootAndIds = async ({ rootId, folderIds }: { rootId: string; folderIds: string[] }, tx?: Knex) => {
|
||||
try {
|
||||
// First, get all descendant folders of rootId
|
||||
const descendants = await (tx || db.replicaNode())
|
||||
.withRecursive("descendants", (qb) =>
|
||||
qb
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretFolder),
|
||||
db.raw("0 as depth"),
|
||||
db.raw(`'/' as path`),
|
||||
db.ref(`${TableName.Environment}.slug`).as("environment")
|
||||
)
|
||||
.from(TableName.SecretFolder)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.where(`${TableName.SecretFolder}.id`, rootId)
|
||||
.union((un) => {
|
||||
void un
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretFolder),
|
||||
db.raw("descendants.depth + 1 as depth"),
|
||||
db.raw(
|
||||
`CONCAT(
|
||||
CASE WHEN descendants.path = '/' THEN '' ELSE descendants.path END,
|
||||
CASE WHEN ${TableName.SecretFolder}."parentId" is NULL THEN '' ELSE CONCAT('/', secret_folders.name) END
|
||||
)`
|
||||
),
|
||||
db.ref("descendants.environment")
|
||||
)
|
||||
.from(TableName.SecretFolder)
|
||||
.where(`${TableName.SecretFolder}.isReserved`, false)
|
||||
.join("descendants", `${TableName.SecretFolder}.parentId`, "descendants.id");
|
||||
})
|
||||
)
|
||||
.select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*")
|
||||
.from("descendants")
|
||||
.whereIn(`id`, folderIds)
|
||||
.orderBy("depth")
|
||||
.orderBy(`name`);
|
||||
|
||||
return descendants;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindFoldersByRootAndIds" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByParentId = async (parentId: string, tx?: Knex) => {
|
||||
try {
|
||||
const folders = await (tx || db.replicaNode())(TableName.SecretFolder)
|
||||
.where({ parentId })
|
||||
.andWhere({ isReserved: false })
|
||||
.select(selectAllTableCols(TableName.SecretFolder));
|
||||
return folders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "findByParentId" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByEnvId = async (envId: string, tx?: Knex) => {
|
||||
try {
|
||||
const folders = await (tx || db.replicaNode())(TableName.SecretFolder)
|
||||
.where({ envId })
|
||||
.andWhere({ isReserved: false })
|
||||
.select(selectAllTableCols(TableName.SecretFolder));
|
||||
return folders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "findByEnvId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretFolderOrm,
|
||||
update,
|
||||
@@ -568,9 +499,6 @@ export const secretFolderDALFactory = (db: TDbClient) => {
|
||||
findClosestFolder,
|
||||
findByProjectId,
|
||||
findByMultiEnv,
|
||||
findByEnvsDeep,
|
||||
findByParentId,
|
||||
findByEnvId,
|
||||
findFoldersByRootAndIds
|
||||
findByEnvsDeep
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||
import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
|
||||
|
||||
import { ChangeType, CommitType, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TSecretFolderDALFactory } from "./secret-folder-dal";
|
||||
@@ -30,8 +29,7 @@ type TSecretFolderServiceFactoryDep = {
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
folderDAL: TSecretFolderDALFactory;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "findBySlugs" | "find">;
|
||||
folderVersionDAL: Pick<TSecretFolderVersionDALFactory, "findLatestFolderVersions" | "create" | "insertMany" | "find">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
folderVersionDAL: TSecretFolderVersionDALFactory;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
};
|
||||
|
||||
@@ -43,7 +41,6 @@ export const secretFolderServiceFactory = ({
|
||||
permissionService,
|
||||
projectEnvDAL,
|
||||
folderVersionDAL,
|
||||
folderCommitService,
|
||||
projectDAL
|
||||
}: TSecretFolderServiceFactoryDep) => {
|
||||
const createFolder = async ({
|
||||
@@ -114,33 +111,15 @@ export const secretFolderServiceFactory = ({
|
||||
});
|
||||
parentFolderId = newFolders.at(-1)?.id as string;
|
||||
const docs = await folderDAL.insertMany(newFolders, tx);
|
||||
const folderVersions = await folderVersionDAL.insertMany(
|
||||
await folderVersionDAL.insertMany(
|
||||
docs.map((doc) => ({
|
||||
name: doc.name,
|
||||
envId: doc.envId,
|
||||
version: doc.version,
|
||||
folderId: doc.id,
|
||||
description: doc.description
|
||||
folderId: doc.id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Folder created",
|
||||
folderId: parentFolderId,
|
||||
changes: folderVersions.map((fv) => ({
|
||||
type: CommitType.ADD,
|
||||
folderVersionId: fv.id
|
||||
}))
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,32 +127,12 @@ export const secretFolderServiceFactory = ({
|
||||
{ name, envId: env.id, version: 1, parentId: parentFolderId, description },
|
||||
tx
|
||||
);
|
||||
const folderVersion = await folderVersionDAL.create(
|
||||
await folderVersionDAL.create(
|
||||
{
|
||||
name: doc.name,
|
||||
envId: doc.envId,
|
||||
version: doc.version,
|
||||
folderId: doc.id,
|
||||
description: doc.description
|
||||
},
|
||||
tx
|
||||
);
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Folder created",
|
||||
folderId: parentFolderId,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
folderVersionId: folderVersion.id
|
||||
}
|
||||
]
|
||||
folderId: doc.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -266,33 +225,12 @@ export const secretFolderServiceFactory = ({
|
||||
{ name, description },
|
||||
tx
|
||||
);
|
||||
const folderVersion = await folderVersionDAL.create(
|
||||
await folderVersionDAL.create(
|
||||
{
|
||||
name: doc.name,
|
||||
envId: doc.envId,
|
||||
version: doc.version,
|
||||
folderId: doc.id,
|
||||
description: doc.description
|
||||
},
|
||||
tx
|
||||
);
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Folder updated",
|
||||
folderId: parentFolder.id,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
isUpdate: true,
|
||||
folderVersionId: folderVersion.id
|
||||
}
|
||||
]
|
||||
folderId: doc.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -383,33 +321,12 @@ export const secretFolderServiceFactory = ({
|
||||
{ name, description },
|
||||
tx
|
||||
);
|
||||
const folderVersion = await folderVersionDAL.create(
|
||||
await folderVersionDAL.create(
|
||||
{
|
||||
name: doc.name,
|
||||
envId: doc.envId,
|
||||
version: doc.version,
|
||||
folderId: doc.id,
|
||||
description: doc.description
|
||||
},
|
||||
tx
|
||||
);
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Folder updated",
|
||||
folderId: parentFolder.id,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.ADD,
|
||||
isUpdate: true,
|
||||
folderVersionId: folderVersion.id
|
||||
}
|
||||
]
|
||||
folderId: doc.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -464,31 +381,7 @@ export const secretFolderServiceFactory = ({
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (!doc) throw new NotFoundError({ message: `Failed to delete folder with ID '${idOrName}', not found` });
|
||||
|
||||
const folderVersions = await folderVersionDAL.findLatestFolderVersions([doc.id], tx);
|
||||
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Folder deleted",
|
||||
folderId: parentFolder.id,
|
||||
changes: [
|
||||
{
|
||||
type: CommitType.DELETE,
|
||||
folderVersionId: folderVersions[doc.id].id,
|
||||
folderId: doc.id
|
||||
}
|
||||
]
|
||||
},
|
||||
tx
|
||||
);
|
||||
return doc;
|
||||
});
|
||||
|
||||
@@ -772,45 +665,6 @@ export const secretFolderServiceFactory = ({
|
||||
return environmentFolders;
|
||||
};
|
||||
|
||||
const getFolderVersionsByIds = async ({
|
||||
folderId,
|
||||
folderVersions
|
||||
}: {
|
||||
folderId: string;
|
||||
folderVersions: string[];
|
||||
}) => {
|
||||
const versions = await folderVersionDAL.find({
|
||||
folderId,
|
||||
$in: {
|
||||
version: folderVersions.map((v) => Number.parseInt(v, 10))
|
||||
}
|
||||
});
|
||||
return versions;
|
||||
};
|
||||
|
||||
const getFolderVersions = async (
|
||||
change: {
|
||||
folderVersion?: string;
|
||||
isUpdate?: boolean;
|
||||
changeType?: string;
|
||||
},
|
||||
fromVersion: string,
|
||||
folderId: string
|
||||
) => {
|
||||
const currentVersion = change.folderVersion || "1";
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const versions = await getFolderVersionsByIds({
|
||||
folderId,
|
||||
folderVersions:
|
||||
change.isUpdate || change.changeType === ChangeType.UPDATE ? [currentVersion, fromVersion] : [currentVersion]
|
||||
});
|
||||
return versions.map((v) => ({
|
||||
version: v.version?.toString() || "1",
|
||||
name: v.name,
|
||||
description: v.description
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
createFolder,
|
||||
updateFolder,
|
||||
@@ -821,8 +675,6 @@ export const secretFolderServiceFactory = ({
|
||||
getProjectFolderCount,
|
||||
getFoldersMultiEnv,
|
||||
getFoldersDeepByEnvs,
|
||||
getProjectEnvironmentsFolders,
|
||||
getFolderVersionsByIds,
|
||||
getFolderVersions
|
||||
getProjectEnvironmentsFolders
|
||||
};
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
||||
const docs: Array<TSecretFolderVersions & { max: number }> = await (tx || db.replicaNode())(
|
||||
TableName.SecretFolderVersion
|
||||
)
|
||||
.whereIn(`${TableName.SecretFolderVersion}.folderId`, folderIds)
|
||||
.whereIn("folderId", folderIds)
|
||||
.join(
|
||||
(tx || db)(TableName.SecretFolderVersion)
|
||||
.groupBy("folderId")
|
||||
@@ -85,8 +85,6 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("folder_cte", "folder_cte.id", `${TableName.SecretFolderVersion}.id`)
|
||||
.whereRaw(`folder_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
// Projects with version >= 3 will require to have all folder versions for PIT
|
||||
.andWhere(`${TableName.Project}.version`, "<", 3)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
@@ -97,107 +95,5 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: pruning secret folder versions completed`);
|
||||
};
|
||||
|
||||
// Get latest versions by folderIds
|
||||
const getLatestFolderVersions = async (folderIds: string[], tx?: Knex): Promise<Array<TSecretFolderVersions>> => {
|
||||
if (!folderIds.length) return [];
|
||||
|
||||
const knexInstance = tx || db.replicaNode();
|
||||
return 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"
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Get specific versions and update with max version
|
||||
const getSpecificFolderVersionsWithLatest = async (
|
||||
versionIds: string[],
|
||||
tx?: Knex
|
||||
): Promise<Array<TSecretFolderVersions>> => {
|
||||
if (!versionIds.length) return [];
|
||||
|
||||
const knexInstance = tx || db.replicaNode();
|
||||
|
||||
// Get specific versions
|
||||
const specificVersions = await knexInstance(TableName.SecretFolderVersion).whereIn("id", versionIds);
|
||||
|
||||
// Get folderIds from these versions
|
||||
const specificFolderIds = [...new Set(specificVersions.map((v) => v.folderId).filter(Boolean))];
|
||||
|
||||
if (!specificFolderIds.length) return specificVersions;
|
||||
|
||||
// Get max versions for these folderIds
|
||||
const maxVersionsQuery = await knexInstance(TableName.SecretFolderVersion)
|
||||
.whereIn("folderId", specificFolderIds)
|
||||
.groupBy("folderId")
|
||||
.select("folderId")
|
||||
.max("version", { as: "maxVersion" });
|
||||
|
||||
// Create lookup map for max versions
|
||||
const maxVersionMap = maxVersionsQuery.reduce<Record<string, number>>((acc, item) => {
|
||||
if (item.maxVersion) {
|
||||
acc[item.folderId] = item.maxVersion;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Replace version with max version
|
||||
return specificVersions.map((version) => ({
|
||||
...version,
|
||||
version: maxVersionMap[version.folderId] || version.version
|
||||
}));
|
||||
};
|
||||
|
||||
const findByIdsWithLatestVersion = async (folderIds: string[], versionIds?: string[], tx?: Knex) => {
|
||||
try {
|
||||
if (!folderIds.length && (!versionIds || !versionIds.length)) return {};
|
||||
|
||||
// Run both queries in parallel
|
||||
const [latestVersions, specificVersionsWithLatest] = await Promise.all([
|
||||
folderIds.length ? getLatestFolderVersions(folderIds, tx) : [],
|
||||
versionIds?.length ? getSpecificFolderVersionsWithLatest(versionIds, tx) : []
|
||||
]);
|
||||
|
||||
const allDocs = [...latestVersions, ...specificVersionsWithLatest];
|
||||
|
||||
// Convert array to record with folderId as key
|
||||
return allDocs.reduce<Record<string, TSecretFolderVersions>>(
|
||||
(prev, curr) => ({ ...prev, [curr.folderId || ""]: curr }),
|
||||
{}
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByIdsWithLatestVersion" });
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
...secretFolderVerOrm,
|
||||
findLatestFolderVersions,
|
||||
findLatestVersionByFolderId,
|
||||
pruneExcessVersions,
|
||||
findByIdsWithLatestVersion,
|
||||
findLatestVersion
|
||||
};
|
||||
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId, pruneExcessVersions };
|
||||
};
|
||||
|
||||
@@ -59,7 +59,6 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
|
||||
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
|
||||
export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
|
||||
|
||||
@@ -95,7 +94,6 @@ type TSecretSyncQueueFactoryDep = {
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
@@ -138,7 +136,6 @@ export const secretSyncQueueFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
licenseService
|
||||
}: TSecretSyncQueueFactoryDep) => {
|
||||
const appCfg = getConfig();
|
||||
@@ -170,8 +167,7 @@ export const secretSyncQueueFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
const $updateManySecretsRawFn = updateManySecretsRawFnFactory({
|
||||
@@ -187,8 +183,7 @@ export const secretSyncQueueFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
const $getInfisicalSecrets = async (
|
||||
@@ -378,7 +373,7 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
if (Object.hasOwn(secretMap, key)) {
|
||||
// Only update secrets if the source value is not empty
|
||||
if (value && value !== secretMap[key].value) {
|
||||
if (value) {
|
||||
secretsToUpdate.push(secret);
|
||||
if (importBehavior === SecretSyncImportBehavior.PrioritizeDestination) importedSecretMap[key] = secretData;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
||||
const secretTagOrm = ormify(db, TableName.SecretTag);
|
||||
const secretJnTagOrm = ormify(db, TableName.JnSecretTag);
|
||||
const secretV2JnTagOrm = ormify(db, TableName.SecretV2JnTag);
|
||||
const secretVersionV2TagOrm = ormify(db, TableName.SecretVersionV2Tag);
|
||||
|
||||
const findManyTagsById = async (projectId: string, ids: string[], tx?: Knex) => {
|
||||
try {
|
||||
@@ -49,39 +48,14 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findSecretTagsByVersionId = async (versionId: string, tx?: Knex) => {
|
||||
try {
|
||||
const tags = await (tx || db.replicaNode())(TableName.SecretVersionV2Tag)
|
||||
.where(`${TableName.SecretVersionV2Tag}.${TableName.SecretVersionV2}Id`, versionId)
|
||||
.select(selectAllTableCols(TableName.SecretVersionV2Tag));
|
||||
return tags;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all by version id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findSecretTagsBySecretId = async (secretId: string, tx?: Knex) => {
|
||||
try {
|
||||
const tags = await (tx || db.replicaNode())(TableName.SecretV2JnTag)
|
||||
.where(`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`, secretId)
|
||||
.select(selectAllTableCols(TableName.SecretV2JnTag));
|
||||
return tags;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all by secret id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretTagOrm,
|
||||
saveTagsToSecret: secretJnTagOrm.insertMany,
|
||||
deleteTagsToSecret: secretJnTagOrm.delete,
|
||||
saveTagsToSecretV2: secretV2JnTagOrm.batchInsert,
|
||||
deleteTagsToSecretV2: secretV2JnTagOrm.delete,
|
||||
saveTagsToSecretVersionV2: secretVersionV2TagOrm.insertMany,
|
||||
findSecretTagsByProjectId,
|
||||
deleteTagsManySecret,
|
||||
findManyTagsById,
|
||||
findSecretTagsByVersionId,
|
||||
findSecretTagsBySecretId
|
||||
findManyTagsById
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import { groupBy } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { CommitType } from "../folder-commit/folder-commit-service";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
||||
@@ -74,7 +73,6 @@ export const fnSecretBulkInsert = async ({
|
||||
resourceMetadataDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderCommitService,
|
||||
actor,
|
||||
tx
|
||||
}: TFnSecretBulkInsert) => {
|
||||
@@ -128,36 +126,11 @@ export const fnSecretBulkInsert = async ({
|
||||
userActorId,
|
||||
identityActorId,
|
||||
actorType,
|
||||
metadata: el.metadata ? JSON.stringify(el.metadata) : [],
|
||||
secretId: newSecretGroupedByKeyName[el.key][0].id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
||||
const commitChanges = secretVersions
|
||||
.filter(({ type }) => type === SecretType.Shared)
|
||||
.map((sv) => ({
|
||||
type: CommitType.ADD,
|
||||
secretVersionId: sv.id
|
||||
}));
|
||||
|
||||
if (commitChanges.length > 0) {
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actorType || ActorType.PLATFORM,
|
||||
metadata: {
|
||||
id: actor?.actorId
|
||||
}
|
||||
},
|
||||
message: "Secret Created",
|
||||
folderId,
|
||||
changes: commitChanges
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
await secretDAL.upsertSecretReferences(
|
||||
inputSecrets.map(({ references = [], key }) => ({
|
||||
secretId: newSecretGroupedByKeyName[key][0].id,
|
||||
@@ -212,7 +185,6 @@ export const fnSecretBulkUpdate = async ({
|
||||
orgId,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
folderCommitService,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
resourceMetadataDAL,
|
||||
@@ -274,7 +246,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
userId,
|
||||
encryptedComment,
|
||||
version,
|
||||
metadata: metadata ? JSON.stringify(metadata) : [],
|
||||
metadata,
|
||||
reminderNote,
|
||||
encryptedValue,
|
||||
reminderRepeatDays,
|
||||
@@ -287,7 +259,6 @@ export const fnSecretBulkUpdate = async ({
|
||||
),
|
||||
tx
|
||||
);
|
||||
|
||||
await secretDAL.upsertSecretReferences(
|
||||
inputSecrets
|
||||
.filter(({ data: { references } }) => Boolean(references))
|
||||
@@ -358,31 +329,6 @@ export const fnSecretBulkUpdate = async ({
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
const commitChanges = secretVersions
|
||||
.filter(({ type }) => type === SecretType.Shared)
|
||||
.map((sv) => ({
|
||||
type: CommitType.ADD,
|
||||
isUpdate: true,
|
||||
secretVersionId: sv.id
|
||||
}));
|
||||
if (commitChanges.length > 0) {
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actorType || ActorType.PLATFORM,
|
||||
metadata: {
|
||||
id: actor?.actorId
|
||||
}
|
||||
},
|
||||
message: "Secret Updated",
|
||||
folderId,
|
||||
changes: commitChanges
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
};
|
||||
|
||||
@@ -391,11 +337,8 @@ export const fnSecretBulkDelete = async ({
|
||||
inputSecrets,
|
||||
tx,
|
||||
actorId,
|
||||
actorType,
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
folderCommitService,
|
||||
secretVersionDAL
|
||||
secretQueueService
|
||||
}: TFnSecretBulkDelete) => {
|
||||
const deletedSecrets = await secretDAL.deleteMany(
|
||||
inputSecrets.map(({ type, secretKey }) => ({
|
||||
@@ -415,35 +358,6 @@ export const fnSecretBulkDelete = async ({
|
||||
)
|
||||
);
|
||||
|
||||
const secretVersions = await secretVersionDAL.findLatestVersionMany(
|
||||
folderId,
|
||||
deletedSecrets.map(({ id }) => id),
|
||||
tx
|
||||
);
|
||||
|
||||
const commitChanges = deletedSecrets
|
||||
.filter(({ type }) => type === SecretType.Shared)
|
||||
.map(({ id }) => ({
|
||||
type: CommitType.DELETE,
|
||||
secretVersionId: secretVersions[id].id
|
||||
}));
|
||||
if (commitChanges.length > 0) {
|
||||
await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actorType || ActorType.PLATFORM,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message: "Secret Deleted",
|
||||
folderId,
|
||||
changes: commitChanges
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
return deletedSecrets;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCommitsActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSet,
|
||||
ProjectPermissionSub
|
||||
@@ -35,7 +34,6 @@ import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
@@ -92,7 +90,6 @@ type TSecretV2BridgeServiceFactoryDep = {
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "findBySlugs">;
|
||||
folderDAL: Pick<
|
||||
TSecretFolderDALFactory,
|
||||
@@ -127,7 +124,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
projectEnvDAL,
|
||||
secretTagDAL,
|
||||
secretVersionDAL,
|
||||
folderCommitService,
|
||||
folderDAL,
|
||||
permissionService,
|
||||
snapshotService,
|
||||
@@ -325,14 +321,12 @@ export const secretV2BridgeServiceFactory = ({
|
||||
userId: inputSecret.type === SecretType.Personal ? actorId : null,
|
||||
tagIds: inputSecret.tagIds,
|
||||
references: nestedReferences,
|
||||
metadata: secretMetadata ? JSON.stringify(secretMetadata) : [],
|
||||
secretMetadata
|
||||
}
|
||||
],
|
||||
resourceMetadataDAL,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
folderCommitService,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
actor: {
|
||||
@@ -516,7 +510,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
folderId,
|
||||
orgId: actorOrgId,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
inputSecrets: [
|
||||
{
|
||||
filter: { id: secretId },
|
||||
@@ -530,7 +523,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
skipMultilineEncoding: inputSecret.skipMultilineEncoding,
|
||||
key: inputSecret.newSecretName || secretName,
|
||||
tags: inputSecret.tagIds,
|
||||
metadata: secretMetadata ? JSON.stringify(secretMetadata) : [],
|
||||
secretMetadata,
|
||||
...encryptedValue
|
||||
}
|
||||
@@ -658,9 +650,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
actorType: actor,
|
||||
folderCommitService,
|
||||
secretVersionDAL,
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: [
|
||||
@@ -1601,7 +1590,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
orgId: actorOrgId,
|
||||
secretDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
@@ -1871,7 +1859,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
const bulkUpdatedSecrets = await fnSecretBulkUpdate({
|
||||
folderId,
|
||||
orgId: actorOrgId,
|
||||
folderCommitService,
|
||||
tx,
|
||||
inputSecrets: secretsToUpdate.map((el) => {
|
||||
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
|
||||
@@ -1941,7 +1928,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
folderCommitService,
|
||||
actor: {
|
||||
type: actor,
|
||||
actorId
|
||||
@@ -2075,8 +2061,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
fnSecretBulkDelete({
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
folderCommitService,
|
||||
secretVersionDAL,
|
||||
inputSecrets: inputSecrets.map(({ type, secretKey }) => ({
|
||||
secretKey,
|
||||
type: type || SecretType.Shared
|
||||
@@ -2084,7 +2068,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
actorType: actor,
|
||||
tx
|
||||
})
|
||||
);
|
||||
@@ -2176,25 +2159,15 @@ export const secretV2BridgeServiceFactory = ({
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const canRead =
|
||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback) ||
|
||||
permission.can(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits);
|
||||
|
||||
if (!canRead) throw new ForbiddenRequestError({ message: "You do not have permission to read secret versions" });
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId: folder.projectId
|
||||
});
|
||||
const secretVersions = await secretVersionDAL.findVersionsBySecretIdWithActors({
|
||||
secretId,
|
||||
projectId: folder.projectId,
|
||||
findOpt: {
|
||||
offset,
|
||||
limit,
|
||||
sort: [["createdAt", "desc"]]
|
||||
}
|
||||
const secretVersions = await secretVersionDAL.findVersionsBySecretIdWithActors(secretId, folder.projectId, {
|
||||
offset,
|
||||
limit,
|
||||
sort: [["createdAt", "desc"]]
|
||||
});
|
||||
return secretVersions.map((el) => {
|
||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||
@@ -2496,7 +2469,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
tx,
|
||||
secretTagDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
secretVersionTagDAL,
|
||||
actor: {
|
||||
type: actor,
|
||||
@@ -2523,7 +2495,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
folderId: destinationFolder.id,
|
||||
orgId: actorOrgId,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
secretVersionDAL,
|
||||
secretDAL,
|
||||
tx,
|
||||
@@ -2869,76 +2840,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getSecretVersionsByIds = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretId,
|
||||
secretVersionNumbers,
|
||||
secretPath,
|
||||
envId,
|
||||
projectId
|
||||
}: TGetSecretVersionsDTO & {
|
||||
secretVersionNumbers: string[];
|
||||
secretPath: string;
|
||||
envId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
const environment = await projectEnvDAL.findOne({ id: envId, projectId });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const canRead =
|
||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback) ||
|
||||
permission.can(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits);
|
||||
|
||||
if (!canRead) throw new ForbiddenRequestError({ message: "You do not have permission to read secret versions" });
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
const secretVersions = await secretVersionDAL.findVersionsBySecretIdWithActors({
|
||||
secretId,
|
||||
projectId,
|
||||
secretVersions: secretVersionNumbers
|
||||
});
|
||||
return secretVersions.map((el) => {
|
||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
{
|
||||
environment: environment.slug,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
...(el.tags?.length && {
|
||||
secretTags: el.tags.map((tag) => tag.slug)
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment.slug,
|
||||
secretPath,
|
||||
{
|
||||
...el,
|
||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||
},
|
||||
secretValueHidden
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createSecret,
|
||||
deleteSecret,
|
||||
@@ -2957,7 +2858,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
getSecretReferenceTree,
|
||||
getSecretsByFolderMappings,
|
||||
getSecretById,
|
||||
getAccessibleSecrets,
|
||||
getSecretVersionsByIds
|
||||
getAccessibleSecrets
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
||||
@@ -179,10 +178,9 @@ export type TFnSecretBulkInsert = {
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "find">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
actor?: {
|
||||
type: string;
|
||||
actorId?: string;
|
||||
actorId: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -208,10 +206,9 @@ export type TFnSecretBulkUpdate = {
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2" | "find">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
actor?: {
|
||||
type: string;
|
||||
actorId?: string;
|
||||
actorId: string;
|
||||
};
|
||||
tx?: Knex;
|
||||
};
|
||||
@@ -221,14 +218,11 @@ export type TFnSecretBulkDelete = {
|
||||
projectId: string;
|
||||
inputSecrets: Array<{ type: SecretType; secretKey: string }>;
|
||||
actorId: string;
|
||||
actorType?: string;
|
||||
tx?: Knex;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
||||
secretQueueService: {
|
||||
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||
};
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "findLatestVersionMany">;
|
||||
};
|
||||
|
||||
export type THandleReminderDTO = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Knex } from "knex";
|
||||
import { TDbClient } from "@app/db";
|
||||
import { SecretVersionsV2Schema, TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas";
|
||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueName } from "@app/queue";
|
||||
|
||||
@@ -138,7 +138,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
{}
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindLatestVersionMany" });
|
||||
throw new DatabaseError({ error, name: "FindLatestVersinMany" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,8 +162,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
||||
.join("version_cte", "version_cte.id", `${TableName.SecretVersionV2}.id`)
|
||||
.whereRaw(`version_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
||||
// Projects with version >= 3 will require to have all secret versions for PIT
|
||||
.andWhere(`${TableName.Project}.version`, "<", 3)
|
||||
.delete();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
@@ -174,21 +172,13 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: pruning secret version v2 completed`);
|
||||
};
|
||||
|
||||
const findVersionsBySecretIdWithActors = async ({
|
||||
secretId,
|
||||
projectId,
|
||||
secretVersions,
|
||||
findOpt = {},
|
||||
tx
|
||||
}: {
|
||||
secretId: string;
|
||||
projectId: string;
|
||||
secretVersions?: string[];
|
||||
findOpt?: TFindOpt<TSecretVersionsV2>;
|
||||
tx?: Knex;
|
||||
}) => {
|
||||
const findVersionsBySecretIdWithActors = async (
|
||||
secretId: string,
|
||||
projectId: string,
|
||||
{ offset, limit, sort = [["createdAt", "desc"]] }: TFindOpt<TSecretVersionsV2> = {},
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt;
|
||||
const query = (tx || db)(TableName.SecretVersionV2)
|
||||
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SecretVersionV2}.userActorId`)
|
||||
.leftJoin(
|
||||
@@ -199,24 +189,22 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
.leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`)
|
||||
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||
.leftJoin(
|
||||
TableName.SecretVersionV2Tag,
|
||||
`${TableName.SecretVersionV2}.id`,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretVersionV2}Id`
|
||||
TableName.SecretV2JnTag,
|
||||
`${TableName.SecretV2}.id`,
|
||||
`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretTag,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretTag}.id`
|
||||
)
|
||||
.where((qb) => {
|
||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
||||
void qb.where(`${TableName.ProjectMembership}.projectId`, projectId);
|
||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||
})
|
||||
.orWhere((qb) => {
|
||||
void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId);
|
||||
void qb.whereNull(`${TableName.ProjectMembership}.projectId`);
|
||||
if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions);
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretVersionV2),
|
||||
@@ -272,178 +260,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Function to fetch latest versions by secretIds
|
||||
const getLatestVersionsBySecretIds = async (
|
||||
folderId: string,
|
||||
secretIds: string[],
|
||||
tx?: Knex
|
||||
): Promise<Array<TSecretVersionsV2>> => {
|
||||
if (!secretIds.length) return [];
|
||||
|
||||
const knexInstance = tx || db.replicaNode();
|
||||
return 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"
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Function to fetch specific versions by versionIds
|
||||
const getSpecificVersionsWithLatestInfo = async (
|
||||
folderId: string,
|
||||
versionIds: string[],
|
||||
tx?: Knex
|
||||
): Promise<Array<TSecretVersionsV2>> => {
|
||||
if (!versionIds.length) return [];
|
||||
|
||||
const knexInstance = tx || db.replicaNode();
|
||||
|
||||
// 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))];
|
||||
|
||||
if (!specificSecretIds.length) return specificVersions;
|
||||
|
||||
// Get max versions for these secretIds
|
||||
const maxVersionsQuery = await knexInstance(TableName.SecretVersionV2)
|
||||
.whereIn("secretId", specificSecretIds)
|
||||
.groupBy("secretId")
|
||||
.select("secretId")
|
||||
.max("version", { as: "maxVersion" });
|
||||
|
||||
// Create a lookup map for max versions
|
||||
const maxVersionMap = maxVersionsQuery.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.secretId] = item.maxVersion;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
|
||||
// Update the version field with maxVersion when needed
|
||||
return specificVersions.map((version) => {
|
||||
// Replace version with maxVersion
|
||||
return {
|
||||
...version,
|
||||
version: maxVersionMap[version.secretId] || version.version
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const findByIdsWithLatestVersion = async (
|
||||
folderId: string,
|
||||
secretIds: string[],
|
||||
versionIds?: string[],
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
if (!secretIds.length && (!versionIds || !versionIds.length)) return {};
|
||||
|
||||
const [latestVersions, specificVersionsWithLatest] = await Promise.all([
|
||||
secretIds.length ? getLatestVersionsBySecretIds(folderId, secretIds, tx) : [],
|
||||
versionIds?.length ? getSpecificVersionsWithLatestInfo(folderId, versionIds, tx) : []
|
||||
]);
|
||||
|
||||
const allDocs = [...latestVersions, ...specificVersionsWithLatest];
|
||||
|
||||
// Convert array to record with secretId as key
|
||||
return allDocs.reduce<Record<string, TSecretVersionsV2>>(
|
||||
(prev, curr) => ({ ...prev, [curr.secretId || ""]: curr }),
|
||||
{}
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByIdsWithLatestVersion" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByIdAndPreviousVersion = async (secretVersionId: string, tx?: Knex) => {
|
||||
try {
|
||||
const targetSecretVersion = await (tx || db.replicaNode())(TableName.SecretVersionV2)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter({ id: secretVersionId }, TableName.SecretVersionV2))
|
||||
.leftJoin(
|
||||
TableName.SecretVersionV2Tag,
|
||||
`${TableName.SecretVersionV2}.id`,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretVersionV2}Id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretTag,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretTag}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.SecretVersionV2))
|
||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||
.first();
|
||||
if (targetSecretVersion) {
|
||||
const previousSecretVersion = await (tx || db.replicaNode())(TableName.SecretVersionV2)
|
||||
.where(
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
buildFindFilter(
|
||||
{ version: targetSecretVersion.version - 1, secretId: targetSecretVersion.secretId },
|
||||
TableName.SecretVersionV2
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretVersionV2Tag,
|
||||
`${TableName.SecretVersionV2}.id`,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretVersionV2}Id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretTag,
|
||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretTag}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.SecretVersionV2))
|
||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||
.first();
|
||||
if (!previousSecretVersion) return [];
|
||||
const docs = [previousSecretVersion, targetSecretVersion];
|
||||
|
||||
const data = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (el) => ({ _id: el.id, ...SecretVersionsV2Schema.parse(el) }),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "tagId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||
id,
|
||||
color,
|
||||
slug,
|
||||
name: slug
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByIdAndPreviousVersion" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretVersionV2Orm,
|
||||
pruneExcessVersions,
|
||||
@@ -451,8 +267,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
bulkUpdate,
|
||||
findLatestVersionByFolderId,
|
||||
findVersionsBySecretIdWithActors,
|
||||
findBySecretId,
|
||||
findByIdsWithLatestVersion,
|
||||
findByIdAndPreviousVersion
|
||||
findBySecretId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -778,7 +778,6 @@ export const createManySecretsRawFnFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
folderCommitService,
|
||||
kmsService,
|
||||
resourceMetadataDAL
|
||||
}: TCreateManySecretsRawFnFactory) => {
|
||||
@@ -851,7 +850,6 @@ export const createManySecretsRawFnFactory = ({
|
||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
|
||||
folderCommitService,
|
||||
tx
|
||||
})
|
||||
);
|
||||
@@ -944,7 +942,6 @@ export const updateManySecretsRawFnFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
kmsService
|
||||
}: TUpdateManySecretsRawFnFactory) => {
|
||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||
@@ -1035,8 +1032,7 @@ export const updateManySecretsRawFnFactory = ({
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
|
||||
folderCommitService
|
||||
secretVersionTagDAL: secretVersionTagV2BridgeDAL
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import { TSecretSyncQueueFactory } from "@app/services/secret-sync/secret-sync-q
|
||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
||||
import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service";
|
||||
@@ -113,7 +112,6 @@ type TSecretQueueFactoryDep = {
|
||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
secretReminderRecipientsDAL: Pick<
|
||||
TSecretReminderRecipientsDALFactory,
|
||||
"delete" | "findUsersBySecretId" | "insertMany" | "transaction"
|
||||
@@ -180,8 +178,7 @@ export const secretQueueFactory = ({
|
||||
projectKeyDAL,
|
||||
resourceMetadataDAL,
|
||||
secretReminderRecipientsDAL,
|
||||
secretSyncQueue,
|
||||
folderCommitService
|
||||
secretSyncQueue
|
||||
}: TSecretQueueFactoryDep) => {
|
||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
|
||||
@@ -369,8 +366,7 @@ export const secretQueueFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
const updateManySecretsRawFn = updateManySecretsRawFnFactory({
|
||||
@@ -386,8 +382,7 @@ export const secretQueueFactory = ({
|
||||
secretVersionV2BridgeDAL,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
resourceMetadataDAL,
|
||||
folderCommitService
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,8 +44,7 @@ import {
|
||||
TGetSecretsRawByFolderMappingsDTO
|
||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
import { ChangeType } from "../folder-commit/folder-commit-service";
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
@@ -2522,36 +2521,6 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getSecretVersionsV2ByIds = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretId,
|
||||
secretVersions,
|
||||
secretPath,
|
||||
envId,
|
||||
projectId
|
||||
}: TGetSecretVersionsDTO & {
|
||||
secretVersions: string[];
|
||||
secretPath: string;
|
||||
envId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
const secretVersionV2 = await secretV2BridgeService.getSecretVersionsByIds({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretId,
|
||||
secretVersionNumbers: secretVersions,
|
||||
secretPath,
|
||||
envId,
|
||||
projectId
|
||||
});
|
||||
return secretVersionV2;
|
||||
};
|
||||
|
||||
const attachTags = async ({
|
||||
secretName,
|
||||
tagSlugs,
|
||||
@@ -3310,53 +3279,6 @@ export const secretServiceFactory = ({
|
||||
return secrets;
|
||||
};
|
||||
|
||||
const getChangeVersions = async (
|
||||
change: {
|
||||
secretVersion: string;
|
||||
secretId?: string;
|
||||
id?: string;
|
||||
isUpdate?: boolean;
|
||||
changeType?: string;
|
||||
},
|
||||
previousVersion: string,
|
||||
actorId: string,
|
||||
actor: ActorType,
|
||||
actorOrgId: string,
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
envId: string,
|
||||
projectId: string,
|
||||
secretPath: string
|
||||
) => {
|
||||
const currentVersion = change.secretVersion;
|
||||
const secretId = change.secretId ? change.secretId : change.id;
|
||||
if (!secretId) {
|
||||
return;
|
||||
}
|
||||
const versions = await getSecretVersionsV2ByIds({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretId,
|
||||
// if it's update add also the previous secretversionid
|
||||
secretVersions:
|
||||
change.isUpdate || change.changeType === ChangeType.UPDATE
|
||||
? [currentVersion, previousVersion]
|
||||
: [currentVersion],
|
||||
secretPath,
|
||||
envId,
|
||||
projectId
|
||||
});
|
||||
return versions?.map((v) => ({
|
||||
secretKey: v.secretKey,
|
||||
secretComment: v.secretComment,
|
||||
skipMultilineEncoding: v.skipMultilineEncoding,
|
||||
tags: v.tags?.map((tag) => tag.slug),
|
||||
metadata: v.metadata,
|
||||
secretValue: v.secretValue
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
attachTags,
|
||||
detachTags,
|
||||
@@ -3387,8 +3309,6 @@ export const secretServiceFactory = ({
|
||||
getSecretsRawByFolderMappings,
|
||||
getSecretAccessList,
|
||||
getSecretByIdRaw,
|
||||
getAccessibleSecrets,
|
||||
getSecretVersionsV2ByIds,
|
||||
getChangeVersions
|
||||
getAccessibleSecrets
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold
|
||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||
@@ -442,7 +441,6 @@ export type TCreateManySecretsRawFnFactory = {
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
};
|
||||
|
||||
export type TCreateManySecretsRawFn = {
|
||||
@@ -480,7 +478,6 @@ export type TUpdateManySecretsRawFnFactory = {
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
};
|
||||
|
||||
export type TUpdateManySecretsRawFn = {
|
||||
|
||||
Reference in New Issue
Block a user