diff --git a/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts b/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts
index b72f5eaa52..41597182cc 100644
--- a/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts
+++ b/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts
@@ -58,6 +58,33 @@ export const registerApprovalPolicyEndpoints =
({
}
});
+ server.route({
+ method: "GET",
+ url: "/",
+ config: {
+ rateLimit: readLimit
+ },
+ schema: {
+ description: "List approval policies",
+ querystring: z.object({
+ projectId: z.string().uuid()
+ }),
+ response: {
+ 200: z.object({
+ policies: z.array(policyResponseSchema)
+ })
+ }
+ },
+ onRequest: verifyAuth([AuthMode.JWT]),
+ handler: async (req) => {
+ const { policies } = await server.services.approvalPolicy.list(policyType, req.query.projectId, req.permission);
+
+ // TODO: Audit log
+
+ return { policies };
+ }
+ });
+
server.route({
method: "GET",
url: "/:policyId",
diff --git a/backend/src/services/approval-policy/approval-policy-dal.ts b/backend/src/services/approval-policy/approval-policy-dal.ts
index 6276b14fc2..bb1854caec 100644
--- a/backend/src/services/approval-policy/approval-policy-dal.ts
+++ b/backend/src/services/approval-policy/approval-policy-dal.ts
@@ -3,7 +3,8 @@ import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
-import { ApproverType } from "./approval-policy-enums";
+import { ApprovalPolicyType, ApproverType } from "./approval-policy-enums";
+import { ApprovalPolicyStep } from "./approval-policy-types";
// Approval Policy
export type TApprovalPolicyDALFactory = ReturnType;
@@ -25,25 +26,23 @@ export const approvalPolicyDALFactory = (db: TDbClient) => {
.whereIn("policyStepId", stepIds)
.select("policyStepId", "userId", "groupId");
- const approversByStepId = approvers.reduce>((acc, approver) => {
- const stepApprovers = acc[approver.policyStepId] || [];
- stepApprovers.push({
- type: approver.userId ? ApproverType.User : ApproverType.Group,
- id: (approver.userId || approver.groupId) as string
- });
- acc[approver.policyStepId] = stepApprovers;
- return acc;
- }, {});
+ const approversByStepId = approvers.reduce>(
+ (acc, approver) => {
+ const stepApprovers = acc[approver.policyStepId] || [];
+ stepApprovers.push({
+ type: approver.userId ? ApproverType.User : ApproverType.Group,
+ id: (approver.userId || approver.groupId) as string
+ });
+ acc[approver.policyStepId] = stepApprovers;
+ return acc;
+ },
+ {}
+ );
return steps.map((step) => {
const stepApprovers = approversByStepId[step.id] || [];
- const formattedStep: {
- name?: string;
- requiredApprovals: number;
- notifyApprovers?: boolean;
- approvers: { type: string; id: string }[];
- } = {
+ const formattedStep: ApprovalPolicyStep = {
requiredApprovals: step.requiredApprovals,
approvers: stepApprovers
};
@@ -62,9 +61,77 @@ export const approvalPolicyDALFactory = (db: TDbClient) => {
}
};
+ const findByProjectId = async (policyType: ApprovalPolicyType, projectId: string) => {
+ try {
+ const dbInstance = db.replicaNode();
+ const policies = await dbInstance(TableName.ApprovalPolicies).where({ type: policyType, projectId });
+
+ if (!policies.length) {
+ return [];
+ }
+
+ const policyIds = policies.map((p) => p.id);
+
+ const steps = await dbInstance(TableName.ApprovalPolicySteps)
+ .whereIn("policyId", policyIds)
+ .orderBy("stepNumber", "asc");
+
+ const stepsByPolicyId: Record = {};
+
+ if (steps.length) {
+ const stepIds = steps.map((step) => step.id);
+
+ const approvers = await dbInstance(TableName.ApprovalPolicyStepApprovers)
+ .whereIn("policyStepId", stepIds)
+ .select("policyStepId", "userId", "groupId");
+
+ const approversByStepId = approvers.reduce>(
+ (acc, approver) => {
+ const stepApprovers = acc[approver.policyStepId] || [];
+ stepApprovers.push({
+ type: approver.userId ? ApproverType.User : ApproverType.Group,
+ id: (approver.userId || approver.groupId) as string
+ });
+ acc[approver.policyStepId] = stepApprovers;
+ return acc;
+ },
+ {}
+ );
+
+ steps.forEach((step) => {
+ const stepApprovers = approversByStepId[step.id] || [];
+ const formattedStep: ApprovalPolicyStep = {
+ requiredApprovals: step.requiredApprovals,
+ approvers: stepApprovers
+ };
+
+ if (step.name) {
+ formattedStep.name = step.name;
+ }
+ if (typeof step.notifyApprovers === "boolean") {
+ formattedStep.notifyApprovers = step.notifyApprovers;
+ }
+
+ if (!stepsByPolicyId[step.policyId]) {
+ stepsByPolicyId[step.policyId] = [];
+ }
+ stepsByPolicyId[step.policyId].push(formattedStep);
+ });
+ }
+
+ return policies.map((policy) => ({
+ ...policy,
+ steps: stepsByPolicyId[policy.id] || []
+ }));
+ } catch (error) {
+ throw new DatabaseError({ error, name: "Find approval policies by project id" });
+ }
+ };
+
return {
...orm,
- findStepsByPolicyId
+ findStepsByPolicyId,
+ findByProjectId
};
};
diff --git a/backend/src/services/approval-policy/approval-policy-service.ts b/backend/src/services/approval-policy/approval-policy-service.ts
index 94dbc3d934..5c16dc7291 100644
--- a/backend/src/services/approval-policy/approval-policy-service.ts
+++ b/backend/src/services/approval-policy/approval-policy-service.ts
@@ -122,6 +122,25 @@ export const approvalPolicyServiceFactory = ({
};
};
+ const list = async (policyType: ApprovalPolicyType, projectId: string, actor: OrgServiceActor) => {
+ const { hasRole } = await permissionService.getProjectPermission({
+ actor: actor.type,
+ actorAuthMethod: actor.authMethod,
+ actorId: actor.id,
+ actorOrgId: actor.orgId,
+ projectId,
+ actionProjectType: ActionProjectType.Any
+ });
+
+ if (!hasRole(ProjectMembershipRole.Admin)) {
+ throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
+ }
+
+ const policies = await approvalPolicyDAL.findByProjectId(policyType, projectId);
+
+ return { policies };
+ };
+
const getById = async (policyId: string, actor: OrgServiceActor) => {
const policy = await approvalPolicyDAL.findById(policyId);
if (!policy) {
@@ -270,6 +289,7 @@ export const approvalPolicyServiceFactory = ({
return {
create,
+ list,
getById,
updateById,
deleteById
diff --git a/backend/src/services/approval-policy/approval-policy-types.ts b/backend/src/services/approval-policy/approval-policy-types.ts
index c2cc858b1f..0c5ffa71f3 100644
--- a/backend/src/services/approval-policy/approval-policy-types.ts
+++ b/backend/src/services/approval-policy/approval-policy-types.ts
@@ -1,4 +1,8 @@
-import { TApprovalPolicyDALFactory, TApprovalRequestGrantsDALFactory } from "./approval-policy-dal";
+import {
+ TApprovalPolicyDALFactory,
+ TApprovalRequestGrantsDALFactory
+} from "@app/services/approval-policy/approval-policy-dal";
+
import { ApprovalPolicyType, ApproverType } from "./approval-policy-enums";
import {
TPamAccessPolicy,
@@ -12,6 +16,16 @@ export type TApprovalPolicyInputs = TPamAccessPolicyInputs;
export type TApprovalPolicyConditions = TPamAccessPolicyConditions;
export type TApprovalPolicyConstraints = TPamAccessPolicyConstraints;
+export interface ApprovalPolicyStep {
+ name?: string | null;
+ requiredApprovals: number;
+ notifyApprovers?: boolean;
+ approvers: {
+ type: ApproverType;
+ id: string;
+ }[];
+}
+
// DTOs
export interface TCreatePolicyDTO {
projectId: TApprovalPolicy["projectId"];
@@ -19,15 +33,7 @@ export interface TCreatePolicyDTO {
maxRequestTtlSeconds?: TApprovalPolicy["maxRequestTtlSeconds"];
conditions: TApprovalPolicy["conditions"]["conditions"];
constraints: TApprovalPolicy["constraints"]["constraints"];
- steps: {
- name?: string | null;
- requiredApprovals: number;
- notifyApprovers?: boolean;
- approvers: {
- type: ApproverType.User | ApproverType.Group;
- id: string;
- }[];
- }[];
+ steps: ApprovalPolicyStep[];
}
export interface TUpdatePolicyDTO {
@@ -35,15 +41,7 @@ export interface TUpdatePolicyDTO {
maxRequestTtlSeconds?: TApprovalPolicy["maxRequestTtlSeconds"];
conditions?: TApprovalPolicy["conditions"]["conditions"];
constraints?: TApprovalPolicy["constraints"]["constraints"];
- steps?: {
- name?: string | null;
- requiredApprovals: number;
- notifyApprovers?: boolean;
- approvers: {
- type: ApproverType.User | ApproverType.Group;
- id: string;
- }[];
- }[];
+ steps?: ApprovalPolicyStep[];
}
// Factory