Type fixes and PIT history pagination

This commit is contained in:
carlosmonastyrski
2025-05-21 23:43:41 -03:00
parent f06004370d
commit 2b948a18f3
16 changed files with 383 additions and 162 deletions
@@ -9,7 +9,7 @@ import {
TSecretFolderVersions,
TSecretVersionsV2
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { DatabaseError, NotFoundError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
export type TFolderCommitDALFactory = ReturnType<typeof folderCommitDALFactory>;
@@ -209,8 +209,8 @@ export const folderCommitDALFactory = (db: TDbClient) => {
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)
.andWhere(`${TableName.FolderCommit}.commitId`, "<=", targetCommitNumber)
.andWhere(`${TableName.FolderCommit}.commitId`, ">", checkpointCommitNumber.toString())
.andWhere(`${TableName.FolderCommit}.commitId`, "<=", targetCommitNumber.toString())
.select(selectAllTableCols(TableName.FolderCommit))
.orderBy(`${TableName.FolderCommit}.commitId`, "asc");
@@ -391,12 +391,89 @@ export const folderCommitDALFactory = (db: TDbClient) => {
.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" });
}
};
return {
...restOfOrm,
findByFolderId,
@@ -411,6 +488,7 @@ export const folderCommitDALFactory = (db: TDbClient) => {
findLatestCommitByFolderIds,
findAllFolderCommitsAfter,
findPreviousCommitTo,
findById
findById,
findByFolderIdPaginated
};
};
@@ -107,7 +107,7 @@ describe("folderCommitServiceFactory", () => {
deleteById: vi.fn().mockResolvedValue({}),
create: vi.fn().mockResolvedValue({}),
updateById: vi.fn().mockResolvedValue({}),
find: vi.fn().mockResolvedValue([]),
find: vi.fn().mockResolvedValue({}), // Changed from [] to {} to match Object.values() expectation
findByIdsWithLatestVersion: vi.fn().mockResolvedValue({})
};
@@ -260,6 +260,17 @@ describe("folderCommitServiceFactory", () => {
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,
@@ -449,6 +460,12 @@ describe("folderCommitServiceFactory", () => {
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
@@ -501,8 +518,8 @@ describe("folderCommitServiceFactory", () => {
const actorType = ActorType.USER;
const differences = [
{ type: "secret", id: "secret-1", versionId: "v1", changeType: ChangeType.CREATE, commitId: 1 },
{ type: "folder", id: "folder-1", versionId: "v2", changeType: ChangeType.UPDATE, commitId: 1 }
{ type: "secret", id: "secret-1", versionId: "v1", changeType: ChangeType.CREATE, commitId: BigInt(1) },
{ type: "folder", id: "folder-1", versionId: "v2", changeType: ChangeType.UPDATE, commitId: BigInt(1) }
];
const secretVersions = {
@@ -76,6 +76,7 @@ export type ResourceChange = {
commitId: bigint;
createdAt?: Date;
parentId?: string;
isUpdate?: boolean;
secretKey?: string;
secretVersion?: string;
secretId?: string;
@@ -997,7 +998,11 @@ export const folderCommitServiceFactory = ({
actorOrgId,
projectId,
environment,
path
path,
offset = 0,
limit = 20,
search,
sort = "desc"
}: {
actor: ActorType;
actorId: string;
@@ -1006,6 +1011,10 @@ export const folderCommitServiceFactory = ({
projectId: string;
environment: string;
path: string;
offset: number;
limit: number;
search?: string;
sort: "asc" | "desc";
}) => {
await checkProjectPermission({
actor,
@@ -1020,7 +1029,12 @@ export const folderCommitServiceFactory = ({
message: `Folder not found for project ID ${projectId}, environment ${environment}, path ${path}`
});
}
const folderCommits = await folderCommitDAL.findByFolderId(folder.id);
const folderCommits = await folderCommitDAL.findByFolderIdPaginated(folder.id, {
offset,
limit,
search,
sort
});
return folderCommits;
};
@@ -1516,6 +1530,9 @@ export const folderCommitServiceFactory = ({
projectId: string;
message?: string;
}) => {
if (!permissionService) {
throw new Error("Permission service not initialized");
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
@@ -1551,8 +1568,11 @@ export const folderCommitServiceFactory = ({
}
// Sort commits by commitId (which appears to be numeric)
const sortedCommits = allCommits.sort((a, b) => a.commitId - b.commitId);
const sortedCommits = allCommits.sort((a, b) => {
if (a.commitId < b.commitId) return -1;
if (a.commitId > b.commitId) return 1;
return 0;
});
// Find the index of the commit to revert
const commitIndex = sortedCommits.findIndex((c) => c.id === commitId);
if (commitIndex === -1) {
@@ -1,7 +1,7 @@
import { z } from "zod";
const secretVersionSchema = z.object({
secretKey: z.string(),
secretKey: z.string().optional().nullable(),
secretComment: z.string().optional().nullable(),
skipMultilineEncoding: z.boolean().optional().nullable(),
secretReminderRepeatDays: z.number().optional().nullable(),
@@ -13,7 +13,7 @@ const secretVersionSchema = z.object({
});
const folderVersionSchema = z.object({
name: z.string()
name: z.string().optional().nullable()
});
const baseChangeSchema = z.object({
@@ -37,46 +37,37 @@ const baseChangeSchema = z.object({
folderId: z.string().optional()
});
const secretChangeSchema = baseChangeSchema.extend({
objectType: z.literal("secret"),
secretVersionId: z.string(),
folderVersionId: z.null(),
folderName: z.null(),
folderChangeId: z.null(),
secretKey: z.string(),
secretVersion: z.union([z.string(), z.number()]),
secretId: z.string(),
versions: z.array(secretVersionSchema).optional()
const commitChangeSchema = baseChangeSchema.extend({
secretVersionId: z.string().optional().nullable(),
folderVersionId: z.string().optional().nullable(),
folderName: z.string().optional().nullable(),
folderChangeId: z.string().optional().nullable(),
secretKey: z.string().optional().nullable(),
secretVersion: z.union([z.string(), z.number()]).optional().nullable(),
secretId: z.string().optional().nullable(),
folderVersion: z.union([z.string(), z.number()]).optional().nullable(),
versions: z.array(z.union([secretVersionSchema, folderVersionSchema])).optional()
});
const folderChangeSchema = baseChangeSchema.extend({
objectType: z.literal("folder"),
secretVersionId: z.null(),
folderVersionId: z.string(),
folderName: z.string(),
folderChangeId: z.string(),
folderVersion: z.union([z.string(), z.number()]),
secretKey: z.null(),
secretId: z.null(),
versions: z.array(folderVersionSchema).optional()
});
const commitChangeSchema = z.discriminatedUnion("objectType", [secretChangeSchema, folderChangeSchema]);
const commitSchema = z.object({
id: z.string(),
commitId: z.string(),
actorMetadata: z.object({
id: z.string(),
name: 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(),
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()]),
changes: z.array(commitChangeSchema)
changes: z.array(commitChangeSchema).optional()
});
export const commitChangesResponseSchema = z.object({