Skip to content

Commit

Permalink
fix: Release channel unique constraint (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
adityachoudhari26 authored Nov 7, 2024
1 parent 2975484 commit 75685e0
Show file tree
Hide file tree
Showing 8 changed files with 4,203 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { usePathname } from "next/navigation";
import { Button } from "@ctrlplane/ui/button";

import { CreateReleaseDialog } from "~/app/[workspaceSlug]/_components/CreateRelease";
import { api } from "~/trpc/react";
import { CreateReleaseChannelDialog } from "./release-channels/CreateReleaseChannelDialog";
import { CreateVariableDialog } from "./releases/CreateVariableDialog";

Expand All @@ -17,6 +18,10 @@ export const NavigationMenuAction: React.FC<{
const isVariablesActive = pathname.includes("variables");
const isReleaseChannelsActive = pathname.includes("release-channels");

const releaseChannelsQ =
api.deployment.releaseChannel.list.byDeploymentId.useQuery(deploymentId);
const releaseChannels = releaseChannelsQ.data ?? [];

return (
<div>
{isVariablesActive && (
Expand All @@ -27,8 +32,11 @@ export const NavigationMenuAction: React.FC<{
</CreateVariableDialog>
)}

{isReleaseChannelsActive && (
<CreateReleaseChannelDialog deploymentId={deploymentId}>
{isReleaseChannelsActive && !releaseChannelsQ.isLoading && (
<CreateReleaseChannelDialog
deploymentId={deploymentId}
releaseChannels={releaseChannels}
>
<Button size="sm" variant="secondary">
New Release Channel
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import type * as SCHEMA from "@ctrlplane/db/schema";
import type { ReleaseCondition } from "@ctrlplane/validators/releases";
import { useState } from "react";
import Link from "next/link";
Expand Down Expand Up @@ -41,23 +42,33 @@ import { api } from "~/trpc/react";

type CreateReleaseChannelDialogProps = {
deploymentId: string;
releaseChannels: SCHEMA.ReleaseChannel[];
children: React.ReactNode;
};

const schema = z.object({
name: z.string().min(1).max(50),
description: z.string().max(1000).optional(),
releaseFilter: releaseCondition
.optional()
.refine((cond) => cond == null || isValidReleaseCondition(cond)),
});

const getFinalFilter = (filter?: ReleaseCondition) =>
filter && !isEmptyCondition(filter) ? filter : undefined;

export const CreateReleaseChannelDialog: React.FC<
CreateReleaseChannelDialogProps
> = ({ deploymentId, children }) => {
> = ({ deploymentId, children, releaseChannels }) => {
// schema needs to be in the component scope to use the releaseChannels
// to validate the name uniqueness
const schema = z.object({
name: z
.string()
.min(1)
.max(50)
.refine(
(name) => !releaseChannels.some((rc) => rc.name === name),
"Release channel name must be unique",
),
description: z.string().max(1000).optional(),
releaseFilter: releaseCondition
.optional()
.refine((cond) => cond == null || isValidReleaseCondition(cond)),
});

const [open, setOpen] = useState(false);
const { workspaceSlug, systemSlug, deploymentSlug } = useParams<{
workspaceSlug: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { User } from "@ctrlplane/db/schema";
import type { z } from "zod";
import { NextResponse } from "next/server";

import { and, eq, takeFirst, takeFirstOrNull } from "@ctrlplane/db";
import { createReleaseChannel } from "@ctrlplane/db/schema";
import * as schema from "@ctrlplane/db/schema";
import { Permission } from "@ctrlplane/validators/auth";
Expand All @@ -27,8 +28,27 @@ export const POST = request()
{ params: { deploymentId: string } }
>(async (ctx, extra) => {
const releaseChannel = await ctx.db
.select()
.from(schema.releaseChannel)
.where(
and(
eq(schema.releaseChannel.deploymentId, extra.params.deploymentId),
eq(schema.releaseChannel.name, ctx.body.name),
),
)
.then(takeFirstOrNull);

if (releaseChannel)
return NextResponse.json(
{ error: "Release channel already exists" },
{ status: 409 },
);

return ctx.db
.insert(schema.releaseChannel)
.values({ ...ctx.body, deploymentId: extra.params.deploymentId })
.returning();
return NextResponse.json(releaseChannel);
.returning()
.then(takeFirst)
.then((releaseChannel) => NextResponse.json(releaseChannel))
.catch((error) => NextResponse.json({ error }, { status: 500 }));
});
26 changes: 22 additions & 4 deletions apps/webservice/src/app/api/v1/release-channels/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { z } from "zod";
import { NextResponse } from "next/server";

import { takeFirst } from "@ctrlplane/db";
import { and, eq, takeFirst, takeFirstOrNull } from "@ctrlplane/db";
import { createReleaseChannel } from "@ctrlplane/db/schema";
import * as SCHEMA from "@ctrlplane/db/schema";
import { Permission } from "@ctrlplane/validators/auth";
Expand All @@ -21,12 +21,30 @@ export const POST = request()
),
)
.handle<{ body: z.infer<typeof createReleaseChannel> }>(
async ({ db, body }) =>
db
async ({ db, body }) => {
const releaseChannel = await db
.select()
.from(SCHEMA.releaseChannel)
.where(
and(
eq(SCHEMA.releaseChannel.deploymentId, body.deploymentId),
eq(SCHEMA.releaseChannel.name, body.name),
),
)
.then(takeFirstOrNull);

if (releaseChannel)
return NextResponse.json(
{ error: "Release channel already exists" },
{ status: 409 },
);

return db
.insert(SCHEMA.releaseChannel)
.values(body)
.returning()
.then(takeFirst)
.then((releaseChannel) => NextResponse.json(releaseChannel))
.catch((error) => NextResponse.json({ error }, { status: 500 })),
.catch((error) => NextResponse.json({ error }, { status: 500 }));
},
);
1 change: 1 addition & 0 deletions packages/db/drizzle/0033_abnormal_microchip.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE UNIQUE INDEX IF NOT EXISTS "release_channel_deployment_id_name_index" ON "release_channel" USING btree ("deployment_id","name");
Loading

0 comments on commit 75685e0

Please sign in to comment.