From 1f2c1ccb9d13dcc28859327db0341342b266e069 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Mon, 5 May 2025 23:15:35 -0700 Subject: [PATCH] chore: clean up checks endpoints --- .../flow-diagram/checks/Approval.tsx | 59 +++----- .../flow-diagram/checks/DenyWindow.tsx | 68 ---------- .../flow-diagram/checks/VersionSelector.tsx | 13 +- .../flow-diagram/nodes/EnvironmentNode.tsx | 2 - .../releases/[releaseId]/checks/page.tsx | 11 +- .../deployment-version/ApprovalDialog.tsx | 3 +- .../deployment-version-checks/approvals.ts | 126 ------------------ .../deployment-version-checks/deny-window.ts | 24 ---- .../deployment-version-checks/router.ts | 41 ------ .../router/deployment-version-checks/utils.ts | 76 ----------- .../version-selector.ts | 71 ---------- packages/api/src/router/deployment-version.ts | 62 ++++++++- 12 files changed, 93 insertions(+), 463 deletions(-) delete mode 100644 apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/DenyWindow.tsx delete mode 100644 packages/api/src/router/deployment-version-checks/approvals.ts delete mode 100644 packages/api/src/router/deployment-version-checks/deny-window.ts delete mode 100644 packages/api/src/router/deployment-version-checks/router.ts delete mode 100644 packages/api/src/router/deployment-version-checks/utils.ts delete mode 100644 packages/api/src/router/deployment-version-checks/version-selector.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/Approval.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/Approval.tsx index edb1dad8e..1212e5af1 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/Approval.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/Approval.tsx @@ -1,10 +1,4 @@ import { Button } from "@ctrlplane/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@ctrlplane/ui/tooltip"; import { ApprovalDialog } from "~/app/[workspaceSlug]/(app)/(deploy)/_components/deployment-version/ApprovalDialog"; import { api } from "~/trpc/react"; @@ -16,16 +10,24 @@ export const ApprovalCheck: React.FC<{ versionId: string; versionTag: string; }> = (props) => { - const { data, isLoading } = - api.deployment.version.checks.approval.status.useQuery(props); + const { data, isLoading } = api.policy.evaluate.useQuery(props); const utils = api.useUtils(); - const invalidate = () => - utils.deployment.version.checks.approval.status.invalidate(props); + const invalidate = () => utils.policy.evaluate.invalidate(props); - const isApproved = data?.approved ?? false; - const rejectionReasonEntries = Array.from( - data?.rejectionReasons.entries() ?? [], - ); + const isAnyApprovalSatisfied = Object.values( + data?.rules.anyApprovals ?? {}, + ).every((reasons) => reasons.length === 0); + const isUserApprovalSatisfied = Object.values( + data?.rules.userApprovals ?? {}, + ).every((reasons) => reasons.length === 0); + const isRoleApprovalSatisfied = Object.values( + data?.rules.roleApprovals ?? {}, + ).every((reasons) => reasons.length === 0); + + const isApproved = + isAnyApprovalSatisfied && + isUserApprovalSatisfied && + isRoleApprovalSatisfied; if (isLoading) return ( @@ -41,35 +43,6 @@ export const ApprovalCheck: React.FC<{ ); - if (rejectionReasonEntries.length > 0) - return ( - - - -
-
- Not enough approvals -
- - - -
-
- -
    - {rejectionReasonEntries.map(([reason, comment]) => ( -
  • - {reason}: {comment} -
  • - ))} -
-
-
-
- ); - return (
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/DenyWindow.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/DenyWindow.tsx deleted file mode 100644 index 7e2f54af1..000000000 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/DenyWindow.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@ctrlplane/ui/tooltip"; - -import { api } from "~/trpc/react"; -import { Loading, Passing, Waiting } from "../StatusIcons"; - -export const DenyWindowCheck: React.FC<{ - workspaceId: string; - environmentId: string; - versionId: string; -}> = ({ workspaceId, environmentId, versionId }) => { - const { data, isLoading } = - api.deployment.version.checks.denyWindow.status.useQuery({ - workspaceId, - environmentId, - versionId, - }); - - const isBlocked = data ?? false; - const rejectionReasonEntries: Array<[string, string]> = []; - - if (isLoading) - return ( -
- Loading deny windows -
- ); - - if (!isBlocked) - return ( -
- no active deny windows -
- ); - - if (rejectionReasonEntries.length > 0) { - return ( - - - -
- deny window is active -
-
- -
    - {rejectionReasonEntries.map(([reason, comment]) => ( -
  • - {reason}: {comment} -
  • - ))} -
-
-
-
- ); - } - - return ( -
- currently in deny window -
- ); -}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/VersionSelector.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/VersionSelector.tsx index e8a47135a..458faccd7 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/VersionSelector.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/checks/VersionSelector.tsx @@ -5,13 +5,14 @@ export const VersionSelectorCheck: React.FC<{ versionId: string; environmentId: string; }> = ({ versionId, environmentId }) => { - const { data, isLoading } = - api.deployment.version.checks.versionSelector.useQuery({ - versionId, - environmentId, - }); + const { data, isLoading } = api.policy.evaluate.useQuery({ + environmentId, + versionId, + }); - const isPassingVersionSelector = data ?? false; + const isPassingVersionSelector = Object.values( + data?.rules.versionSelector ?? {}, + ).every((isPassing) => isPassing); if (isLoading) return ( diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/nodes/EnvironmentNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/nodes/EnvironmentNode.tsx index a996f81ad..93b494ffb 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/nodes/EnvironmentNode.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/_components/flow-diagram/nodes/EnvironmentNode.tsx @@ -8,7 +8,6 @@ import { cn } from "@ctrlplane/ui"; import { Separator } from "@ctrlplane/ui/separator"; import { ApprovalCheck } from "../checks/Approval"; -import { DenyWindowCheck } from "../checks/DenyWindow"; import { VersionSelectorCheck } from "../checks/VersionSelector"; type EnvironmentNodeProps = NodeProps<{ @@ -35,7 +34,6 @@ export const EnvironmentNode: React.FC = ({ data }) => (
-
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/page.tsx index 1172d2dd9..57200446c 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/(raw)/releases/[releaseId]/checks/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { IconMenu2 } from "@tabler/icons-react"; +import _ from "lodash"; import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; @@ -44,9 +45,15 @@ export default async function ChecksPage(props: PageProps) { if (deploymentVersion == null || deployment == null) return notFound(); const { system } = deployment; - const environments = await api.deployment.version.checks.environmentsToCheck( - deployment.id, + const releaseTargets = await api.releaseTarget.list({ + deploymentId: deployment.id, + }); + + const environments = _.uniqBy( + releaseTargets.map((r) => r.environment), + (env) => env.id, ); + return (
void; }> = ({ versionId, versionTag, environmentId, children, onSubmit }) => { const [open, setOpen] = useState(false); - const addRecord = - api.deployment.version.checks.approval.addRecord.useMutation(); + const addRecord = api.deployment.version.addApprovalRecord.useMutation(); const router = useRouter(); diff --git a/packages/api/src/router/deployment-version-checks/approvals.ts b/packages/api/src/router/deployment-version-checks/approvals.ts deleted file mode 100644 index 60650345a..000000000 --- a/packages/api/src/router/deployment-version-checks/approvals.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { z } from "zod"; - -import { and, eq } from "@ctrlplane/db"; -import * as SCHEMA from "@ctrlplane/db/schema"; -import { Channel, getQueue } from "@ctrlplane/events"; -import { - getVersionApprovalRules, - mergePolicies, - VersionReleaseManager, -} from "@ctrlplane/rule-engine"; -import { Permission } from "@ctrlplane/validators/auth"; - -import { createTRPCRouter, protectedProcedure } from "../../trpc"; -import { - getAnyReleaseTargetForDeploymentAndEnvironment, - getApplicablePoliciesWithoutResourceScope, - getVersionWithMetadata, -} from "./utils"; - -export const approvalRouter = createTRPCRouter({ - status: protectedProcedure - .input( - z.object({ - workspaceId: z.string().uuid(), - versionId: z.string().uuid(), - environmentId: z.string().uuid(), - }), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser.perform(Permission.DeploymentVersionGet).on({ - type: "deploymentVersion", - id: input.versionId, - }), - }) - .query( - async ({ ctx, input: { versionId, environmentId, workspaceId } }) => { - const version = await getVersionWithMetadata(ctx.db, versionId); - const { deploymentId } = version; - - const releaseTarget = - await getAnyReleaseTargetForDeploymentAndEnvironment( - ctx.db, - deploymentId, - environmentId, - workspaceId, - ); - const policies = await getApplicablePoliciesWithoutResourceScope( - ctx.db, - releaseTarget.id, - ); - const mergedPolicy = mergePolicies(policies); - const manager = new VersionReleaseManager(ctx.db, releaseTarget); - const result = await manager.evaluate({ - policy: mergedPolicy ?? undefined, - versions: [version], - rules: getVersionApprovalRules, - }); - - return { - approved: result.chosenCandidate != null, - rejectionReasons: result.rejectionReasons, - }; - }, - ), - - addRecord: protectedProcedure - .input( - z.object({ - deploymentVersionId: z.string().uuid(), - environmentId: z.string().uuid(), - status: z.nativeEnum(SCHEMA.ApprovalStatus), - reason: z.string().optional(), - }), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser.perform(Permission.DeploymentVersionGet).on({ - type: "deploymentVersion", - id: input.deploymentVersionId, - }), - }) - .mutation(async ({ ctx, input }) => { - const { deploymentVersionId, environmentId, status, reason } = input; - - const record = await ctx.db - .insert(SCHEMA.policyRuleAnyApprovalRecord) - .values({ - deploymentVersionId, - userId: ctx.session.user.id, - status, - reason, - approvedAt: - status === SCHEMA.ApprovalStatus.Approved ? new Date() : null, - }) - .returning(); - - const rows = await ctx.db - .select() - .from(SCHEMA.deploymentVersion) - .innerJoin( - SCHEMA.releaseTarget, - eq( - SCHEMA.deploymentVersion.deploymentId, - SCHEMA.releaseTarget.deploymentId, - ), - ) - .where( - and( - eq(SCHEMA.deploymentVersion.id, deploymentVersionId), - eq(SCHEMA.releaseTarget.environmentId, environmentId), - ), - ); - - const targets = rows.map((row) => row.release_target); - if (targets.length > 0) - await getQueue(Channel.EvaluateReleaseTarget).addBulk( - targets.map((rt) => ({ - name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`, - data: rt, - })), - ); - - return record; - }), -}); diff --git a/packages/api/src/router/deployment-version-checks/deny-window.ts b/packages/api/src/router/deployment-version-checks/deny-window.ts deleted file mode 100644 index ee16ede2a..000000000 --- a/packages/api/src/router/deployment-version-checks/deny-window.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from "zod"; - -import { Permission } from "@ctrlplane/validators/auth"; - -import { createTRPCRouter, protectedProcedure } from "../../trpc"; - -export const denyWindowRouter = createTRPCRouter({ - status: protectedProcedure - .input( - z.object({ - workspaceId: z.string().uuid(), - versionId: z.string().uuid(), - environmentId: z.string().uuid(), - }), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser.perform(Permission.DeploymentVersionGet).on({ - type: "deploymentVersion", - id: input.versionId, - }), - }) - .query(() => false), -}); diff --git a/packages/api/src/router/deployment-version-checks/router.ts b/packages/api/src/router/deployment-version-checks/router.ts deleted file mode 100644 index 03b652003..000000000 --- a/packages/api/src/router/deployment-version-checks/router.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { z } from "zod"; - -import { eq } from "@ctrlplane/db"; -import * as SCHEMA from "@ctrlplane/db/schema"; -import { Permission } from "@ctrlplane/validators/auth"; - -import { createTRPCRouter, protectedProcedure } from "../../trpc"; -import { approvalRouter } from "./approvals"; -import { denyWindowRouter } from "./deny-window"; -import { versionSelector } from "./version-selector"; - -export const deploymentVersionChecksRouter = createTRPCRouter({ - environmentsToCheck: protectedProcedure - .input(z.string().uuid()) - .meta({ - authorizationCheck: async ({ ctx, canUser, input }) => { - const deployment = await ctx.db.query.deployment.findFirst({ - where: eq(SCHEMA.deployment.id, input), - }); - if (deployment == null) return false; - return canUser - .perform(Permission.EnvironmentList) - .on({ type: "system", id: deployment.systemId }); - }, - }) - .query(async ({ ctx, input: deploymentId }) => { - const rows = await ctx.db - .selectDistinctOn([SCHEMA.environment.id]) - .from(SCHEMA.releaseTarget) - .innerJoin( - SCHEMA.environment, - eq(SCHEMA.releaseTarget.environmentId, SCHEMA.environment.id), - ) - .where(eq(SCHEMA.releaseTarget.deploymentId, deploymentId)) - .orderBy(SCHEMA.environment.id); - return rows.map((r) => r.environment); - }), - approval: approvalRouter, - denyWindow: denyWindowRouter, - versionSelector, -}); diff --git a/packages/api/src/router/deployment-version-checks/utils.ts b/packages/api/src/router/deployment-version-checks/utils.ts deleted file mode 100644 index 0fec22a1e..000000000 --- a/packages/api/src/router/deployment-version-checks/utils.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { Tx } from "@ctrlplane/db"; -import { TRPCError } from "@trpc/server"; - -import { and, eq, inArray, isNull } from "@ctrlplane/db"; -import * as SCHEMA from "@ctrlplane/db/schema"; - -export const getApplicablePoliciesWithoutResourceScope = async ( - db: Tx, - releaseTargetId: string, -) => { - const rows = await db - .select() - .from(SCHEMA.computedPolicyTargetReleaseTarget) - .innerJoin( - SCHEMA.policyTarget, - eq( - SCHEMA.computedPolicyTargetReleaseTarget.policyTargetId, - SCHEMA.policyTarget.id, - ), - ) - .where( - and( - eq( - SCHEMA.computedPolicyTargetReleaseTarget.releaseTargetId, - releaseTargetId, - ), - isNull(SCHEMA.policyTarget.resourceSelector), - ), - ); - - const policyIds = rows.map((r) => r.policy_target.policyId); - return db.query.policy.findMany({ - where: inArray(SCHEMA.policy.id, policyIds), - with: { - denyWindows: true, - deploymentVersionSelector: true, - versionAnyApprovals: true, - versionRoleApprovals: true, - versionUserApprovals: true, - }, - }); -}; - -export const getVersionWithMetadata = async (db: Tx, versionId: string) => { - const v = await db.query.deploymentVersion.findFirst({ - where: eq(SCHEMA.deploymentVersion.id, versionId), - with: { metadata: true }, - }); - if (v == null) - throw new TRPCError({ - code: "NOT_FOUND", - message: `Deployment version not found: ${versionId}`, - }); - const metadata = Object.fromEntries(v.metadata.map((m) => [m.key, m.value])); - return { ...v, metadata }; -}; - -export const getAnyReleaseTargetForDeploymentAndEnvironment = async ( - db: Tx, - deploymentId: string, - environmentId: string, - workspaceId: string, -) => { - const rt = await db.query.releaseTarget.findFirst({ - where: and( - eq(SCHEMA.releaseTarget.deploymentId, deploymentId), - eq(SCHEMA.releaseTarget.environmentId, environmentId), - ), - }); - if (rt == null) - throw new TRPCError({ - code: "NOT_FOUND", - message: `Release target not found: ${deploymentId} ${environmentId}`, - }); - return { ...rt, workspaceId }; -}; diff --git a/packages/api/src/router/deployment-version-checks/version-selector.ts b/packages/api/src/router/deployment-version-checks/version-selector.ts deleted file mode 100644 index 4f87bb95d..000000000 --- a/packages/api/src/router/deployment-version-checks/version-selector.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { TRPCError } from "@trpc/server"; -import { z } from "zod"; - -import { and, eq, selector } from "@ctrlplane/db"; -import * as SCHEMA from "@ctrlplane/db/schema"; -import { mergePolicies } from "@ctrlplane/rule-engine"; -import { Permission } from "@ctrlplane/validators/auth"; - -import { protectedProcedure } from "../../trpc"; -import { - getAnyReleaseTargetForDeploymentAndEnvironment, - getApplicablePoliciesWithoutResourceScope, - getVersionWithMetadata, -} from "./utils"; - -export const versionSelector = protectedProcedure - .input( - z.object({ - versionId: z.string().uuid(), - environmentId: z.string().uuid(), - }), - ) - .meta({ - authorizationCheck: ({ canUser, input }) => - canUser.perform(Permission.DeploymentVersionGet).on({ - type: "deploymentVersion", - id: input.versionId, - }), - }) - .query(async ({ ctx, input }) => { - const { versionId, environmentId } = input; - const version = await getVersionWithMetadata(ctx.db, versionId); - const { deploymentId } = version; - - const environment = await ctx.db.query.environment.findFirst({ - where: eq(SCHEMA.environment.id, environmentId), - with: { system: true }, - }); - if (environment == null) - throw new TRPCError({ - code: "NOT_FOUND", - message: `Environment not found: ${environmentId}`, - }); - - const { system } = environment; - const { workspaceId } = system; - - const releaseTarget = await getAnyReleaseTargetForDeploymentAndEnvironment( - ctx.db, - deploymentId, - environmentId, - workspaceId, - ); - const policies = await getApplicablePoliciesWithoutResourceScope( - ctx.db, - releaseTarget.id, - ); - const mergedPolicy = mergePolicies(policies); - const versionSelector = - mergedPolicy?.deploymentVersionSelector?.deploymentVersionSelector; - if (versionSelector == null) return true; - - const matchedVersion = await ctx.db.query.deploymentVersion.findFirst({ - where: and( - eq(SCHEMA.deploymentVersion.id, versionId), - selector().query().deploymentVersions().where(versionSelector).sql(), - ), - }); - - return matchedVersion != null; - }); diff --git a/packages/api/src/router/deployment-version.ts b/packages/api/src/router/deployment-version.ts index 1b78c6427..94d4613e2 100644 --- a/packages/api/src/router/deployment-version.ts +++ b/packages/api/src/router/deployment-version.ts @@ -45,7 +45,6 @@ import { } from "@ctrlplane/validators/releases"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { deploymentVersionChecksRouter } from "./deployment-version-checks/router"; import { deploymentVersionJobsRouter } from "./deployment-version-jobs"; import { deploymentVersionMetadataKeysRouter } from "./version-metadata-keys"; @@ -379,6 +378,66 @@ export const versionRouter = createTRPCRouter({ ), ), + addApprovalRecord: protectedProcedure + .input( + z.object({ + deploymentVersionId: z.string().uuid(), + environmentId: z.string().uuid(), + status: z.nativeEnum(SCHEMA.ApprovalStatus), + reason: z.string().optional(), + }), + ) + .meta({ + authorizationCheck: ({ canUser, input }) => + canUser.perform(Permission.DeploymentVersionGet).on({ + type: "deploymentVersion", + id: input.deploymentVersionId, + }), + }) + .mutation(async ({ ctx, input }) => { + const { deploymentVersionId, environmentId, status, reason } = input; + + const record = await ctx.db + .insert(SCHEMA.policyRuleAnyApprovalRecord) + .values({ + deploymentVersionId, + userId: ctx.session.user.id, + status, + reason, + approvedAt: + status === SCHEMA.ApprovalStatus.Approved ? new Date() : null, + }) + .returning(); + + const rows = await ctx.db + .select() + .from(SCHEMA.deploymentVersion) + .innerJoin( + SCHEMA.releaseTarget, + eq( + SCHEMA.deploymentVersion.deploymentId, + SCHEMA.releaseTarget.deploymentId, + ), + ) + .where( + and( + eq(SCHEMA.deploymentVersion.id, deploymentVersionId), + eq(SCHEMA.releaseTarget.environmentId, environmentId), + ), + ); + + const targets = rows.map((row) => row.release_target); + if (targets.length > 0) + await getQueue(Channel.EvaluateReleaseTarget).addBulk( + targets.map((rt) => ({ + name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`, + data: rt, + })), + ); + + return record; + }), + /** * Lists all environments where a deployment version is blocked from being deployed based on policy rules. * This is crucial for determining where a version cannot be released due to environment-specific policies and deployment rules. @@ -834,5 +893,4 @@ export const versionRouter = createTRPCRouter({ }), metadataKeys: deploymentVersionMetadataKeysRouter, - checks: deploymentVersionChecksRouter, });