feat(#40): add domain generation by traefik.me

This commit is contained in:
Mauricio Siu
2024-06-24 00:47:40 -06:00
parent 687524d154
commit f230bda1f7
4 changed files with 122 additions and 11 deletions

View File

@@ -0,0 +1,69 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { RefreshCcw, TrashIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {
applicationId: string;
}
export const GenerateDomain = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } = api.domain.generateDomain.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="secondary" isLoading={isLoading}>
Generate Domain
<RefreshCcw className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure to generate a new domain?
</AlertDialogTitle>
<AlertDialogDescription>
This will generate a new domain and will be used to access to the
application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
applicationId,
})
.then((data) => {
utils.domain.byApplicationId.invalidate({
applicationId: applicationId,
});
utils.application.readTraefikConfig.invalidate({
applicationId: applicationId,
});
toast.success("Generated Domain succesfully");
})
.catch(() => {
toast.error("Error to generate Domain");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -6,7 +6,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ExternalLink, GlobeIcon } from "lucide-react";
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { Input } from "@/components/ui/input";
@@ -14,6 +14,7 @@ import { DeleteDomain } from "./delete-domain";
import Link from "next/link";
import { AddDomain } from "./add-domain";
import { UpdateDomain } from "./update-domain";
import { GenerateDomain } from "./generate-domain";
interface Props {
applicationId: string;
@@ -31,7 +32,7 @@ export const ShowDomains = ({ applicationId }: Props) => {
return (
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader className="flex flex-row items-center justify-between">
<CardHeader className="flex flex-row items-center flex-wrap gap-4 justify-between">
<div className="flex flex-col gap-1">
<CardTitle className="text-xl">Domains</CardTitle>
<CardDescription>
@@ -39,11 +40,16 @@ export const ShowDomains = ({ applicationId }: Props) => {
</CardDescription>
</div>
{data && data?.length > 0 && (
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
)}
<div className="flex flex-row gap-4 flex-wrap">
{data && data?.length > 0 && (
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
)}
{data && data?.length > 0 && (
<GenerateDomain applicationId={applicationId} />
)}
</div>
</CardHeader>
<CardContent className="flex w-full flex-row gap-4">
{data?.length === 0 ? (
@@ -53,9 +59,13 @@ export const ShowDomains = ({ applicationId }: Props) => {
To access to the application is required to set at least 1
domain
</span>
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
<div className="flex flex-row gap-4 flex-wrap">
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
<GenerateDomain applicationId={applicationId} />
</div>
</div>
) : (
<div className="flex w-full flex-col gap-4">

View File

@@ -12,6 +12,7 @@ import {
createDomain,
findDomainById,
findDomainsByApplicationId,
generateDomain,
removeDomainById,
updateDomainById,
} from "../services/domain";
@@ -35,6 +36,11 @@ export const domainRouter = createTRPCRouter({
.query(async ({ input }) => {
return await findDomainsByApplicationId(input.applicationId);
}),
generateDomain: protectedProcedure
.input(apiFindDomainByApplication)
.mutation(async ({ input }) => {
return generateDomain(input);
}),
update: protectedProcedure
.input(apiUpdateDomain)
.mutation(async ({ input }) => {

View File

@@ -1,9 +1,15 @@
import { db } from "@/server/db";
import { type apiCreateDomain, domains } from "@/server/db/schema";
import {
type apiCreateDomain,
type apiFindDomainByApplication,
domains,
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { findApplicationById } from "./application";
import { manageDomain } from "@/server/utils/traefik/domain";
import { findAdmin } from "./admin";
import { generateRandomDomain } from "@/templates/utils";
export type Domain = typeof domains.$inferSelect;
@@ -29,6 +35,26 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
await manageDomain(application, domain);
});
};
export const generateDomain = async (
input: typeof apiFindDomainByApplication._type,
) => {
const application = await findApplicationById(input.applicationId);
const admin = await findAdmin();
const domain = await createDomain({
applicationId: application.applicationId,
host: generateRandomDomain({
serverIp: admin.serverIp || "",
projectName: application.appName,
}),
port: process.env.NODE_ENV === "development" ? 3000 : 80,
certificateType: "none",
https: false,
path: "/",
});
return domain;
};
export const findDomainById = async (domainId: string) => {
const domain = await db.query.domains.findFirst({
where: eq(domains.domainId, domainId),