From fae180f157c33ef55d61d32722236aa1c635082c Mon Sep 17 00:00:00 2001 From: hehehai Date: Fri, 24 May 2024 15:43:05 +0800 Subject: [PATCH 1/9] feat: server support custom name --- .../dashboard/project/add-application.tsx | 77 +++-- components/dashboard/project/add-database.tsx | 283 +++++++++--------- pages/dashboard/project/[projectId].tsx | 7 +- server/api/routers/application.ts | 5 +- server/api/routers/mariadb.ts | 3 + server/api/routers/mongo.ts | 3 + server/api/routers/mysql.ts | 3 + server/api/routers/postgres.ts | 3 + server/api/services/application.ts | 12 + server/api/services/mariadb.ts | 12 + server/api/services/mongo.ts | 12 + server/api/services/mysql.ts | 13 +- server/api/services/postgres.ts | 12 + server/api/services/project.ts | 49 ++- server/api/services/redis.ts | 12 + server/db/schema/application.ts | 1 + server/db/schema/mariadb.ts | 1 + server/db/schema/mongo.ts | 1 + server/db/schema/mysql.ts | 1 + server/db/schema/postgres.ts | 1 + server/db/schema/redis.ts | 1 + 21 files changed, 334 insertions(+), 178 deletions(-) diff --git a/components/dashboard/project/add-application.tsx b/components/dashboard/project/add-application.tsx index 7edc10b82..3eb66b1af 100644 --- a/components/dashboard/project/add-application.tsx +++ b/components/dashboard/project/add-application.tsx @@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { AlertTriangle, Folder } from "lucide-react"; -import { useEffect } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -31,6 +31,15 @@ const AddTemplateSchema = z.object({ name: z.string().min(1, { message: "Name is required", }), + appName: z + .string() + .min(1, { + message: "App name is required", + }) + .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { + message: + "App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", + }), description: z.string().optional(), }); @@ -38,10 +47,12 @@ type AddTemplate = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddApplication = ({ projectId }: Props) => { +export const AddApplication = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); + const [visible, setVisible] = useState(false); const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -49,34 +60,34 @@ export const AddApplication = ({ projectId }: Props) => { const form = useForm({ defaultValues: { name: "", + appName: `${projectName}-`, description: "", }, resolver: zodResolver(AddTemplateSchema), }); - useEffect(() => { - form.reset(); - }, [form, form.reset, form.formState.isSubmitSuccessful]); - const onSubmit = async (data: AddTemplate) => { await mutateAsync({ name: data.name, + appName: data.appName, description: data.description, projectId, }) .then(async () => { toast.success("Service Created"); + form.reset(); + setVisible(false); await utils.project.one.invalidate({ projectId, }); }) - .catch(() => { + .catch((e) => { toast.error("Error to create the service"); }); }; return ( - + { onSubmit={form.handleSubmit(onSubmit)} className="grid w-full gap-4" > -
- ( - - Name - - - - - - - )} - /> -
+ ( + + Name + + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${projectName}-${val}`); + field.onChange(val); + }} + /> + + + + )} + /> + ( + + AppName + + + + + + )} + /> , + label: "PostgreSQL", + }, + mongo: { + icon: , + label: "MongoDB", + }, + mariadb: { + icon: , + label: "MariaDB", + }, + mysql: { + icon: , + label: "MySQL", + }, + redis: { + icon: , + label: "Redis", + }, +}; + type AddDatabase = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddDatabase = ({ projectId }: Props) => { +export const AddDatabase = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); + const [visible, setVisible] = useState(false); - const { mutateAsync: createPostgresql } = api.postgres.create.useMutation(); - - const { mutateAsync: createMongo } = api.mongo.create.useMutation(); - - const { mutateAsync: createRedis } = api.redis.create.useMutation(); - - const { mutateAsync: createMariadb } = api.mariadb.create.useMutation(); - - const { mutateAsync: createMysql } = api.mysql.create.useMutation(); + const postgresMutation = api.postgres.create.useMutation(); + const mongoMutation = api.mongo.create.useMutation(); + const redisMutation = api.redis.create.useMutation(); + const mariadbMutation = api.mariadb.create.useMutation(); + const mysqlMutation = api.mysql.create.useMutation(); const form = useForm({ defaultValues: { type: "postgres", dockerImage: "", name: "", + appName: `${projectName}-`, databasePassword: "", description: "", databaseName: "", @@ -133,76 +164,65 @@ export const AddDatabase = ({ projectId }: Props) => { resolver: zodResolver(mySchema), }); const type = form.watch("type"); - - useEffect(() => { - form.reset({ - type: "postgres", - dockerImage: "", - name: "", - databasePassword: "", - description: "", - databaseName: "", - databaseUser: "", - }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + const activeMutation = { + postgres: postgresMutation, + mongo: mongoMutation, + redis: redisMutation, + mariadb: mariadbMutation, + mysql: mysqlMutation, + }; const onSubmit = async (data: AddDatabase) => { const defaultDockerImage = data.dockerImage || dockerImageDefaultPlaceholder[data.type]; let promise: Promise | null = null; + const commonParams = { + name: data.name, + appName: data.appName, + dockerImage: defaultDockerImage, + projectId, + description: data.description, + }; + if (data.type === "postgres") { - promise = createPostgresql({ - name: data.name, - dockerImage: defaultDockerImage, + promise = postgresMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, - description: data.description, }); } else if (data.type === "mongo") { - promise = createMongo({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mongoMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, - description: data.description, }); } else if (data.type === "redis") { - promise = createRedis({ - name: data.name, - dockerImage: defaultDockerImage, + promise = redisMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, projectId, - description: data.description, }); } else if (data.type === "mariadb") { - promise = createMariadb({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mariadbMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, - projectId, databaseRootPassword: data.databaseRootPassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - description: data.description, }); } else if (data.type === "mysql") { - promise = createMysql({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mysqlMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, databaseRootPassword: data.databaseRootPassword, - description: data.description, }); } @@ -210,6 +230,17 @@ export const AddDatabase = ({ projectId }: Props) => { await promise .then(async () => { toast.success("Database Created"); + form.reset({ + type: "postgres", + dockerImage: "", + name: "", + appName: `${projectName}-`, + databasePassword: "", + description: "", + databaseName: "", + databaseUser: "", + }); + setVisible(false); await utils.project.one.invalidate({ projectId, }); @@ -220,7 +251,7 @@ export const AddDatabase = ({ projectId }: Props) => { } }; return ( - + { Database - + Databases - {/* {isError && ( -
- - - {error?.message} - -
- )} */}
{ defaultValue={field.value} className="grid w-full grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4" > - - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
+ {Object.entries(databasesMap).map(([key, value]) => ( + + +
+ + +
+
+
+ ))} + {activeMutation[field.value].isError && ( +
+ + + {activeMutation[field.value].error?.message} + +
+ )} )} /> @@ -372,13 +336,34 @@ export const AddDatabase = ({ projectId }: Props) => { Name - + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${projectName}-${val}`); + field.onChange(val); + }} + /> )} /> + ( + + AppName + + + + + + )} + /> - - + + )} diff --git a/server/api/routers/application.ts b/server/api/routers/application.ts index 304d04498..dd23d2aaa 100644 --- a/server/api/routers/application.ts +++ b/server/api/routers/application.ts @@ -65,7 +65,10 @@ export const applicationRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await addNewService(ctx.user.authId, newApplication.applicationId); } - } catch (error) { + } catch (error: unknown) { + if (error instanceof TRPCError) { + throw error; + } throw new TRPCError({ code: "BAD_REQUEST", message: "Error to create the application", diff --git a/server/api/routers/mariadb.ts b/server/api/routers/mariadb.ts index 59e577483..2ab8dd6a9 100644 --- a/server/api/routers/mariadb.ts +++ b/server/api/routers/mariadb.ts @@ -49,6 +49,9 @@ export const mariadbRouter = createTRPCRouter({ return true; } catch (error) { + if (error instanceof TRPCError) { + throw error; + } throw new TRPCError({ code: "BAD_REQUEST", message: "Error input: Inserting mariadb database", diff --git a/server/api/routers/mongo.ts b/server/api/routers/mongo.ts index 705549b67..d9ddd2c27 100644 --- a/server/api/routers/mongo.ts +++ b/server/api/routers/mongo.ts @@ -49,6 +49,9 @@ export const mongoRouter = createTRPCRouter({ return true; } catch (error) { + if (error instanceof TRPCError) { + throw error; + } throw new TRPCError({ code: "BAD_REQUEST", message: "Error input: Inserting mongo database", diff --git a/server/api/routers/mysql.ts b/server/api/routers/mysql.ts index 02f683baa..f520064b3 100644 --- a/server/api/routers/mysql.ts +++ b/server/api/routers/mysql.ts @@ -50,6 +50,9 @@ export const mysqlRouter = createTRPCRouter({ return true; } catch (error) { + if (error instanceof TRPCError) { + throw error; + } throw new TRPCError({ code: "BAD_REQUEST", message: "Error input: Inserting mysql database", diff --git a/server/api/routers/postgres.ts b/server/api/routers/postgres.ts index 45bd88a1a..4dc7ff5d7 100644 --- a/server/api/routers/postgres.ts +++ b/server/api/routers/postgres.ts @@ -49,6 +49,9 @@ export const postgresRouter = createTRPCRouter({ return true; } catch (error) { + if (error instanceof TRPCError) { + throw error; + } throw new TRPCError({ code: "BAD_REQUEST", message: "Error input: Inserting postgresql database", diff --git a/server/api/services/application.ts b/server/api/services/application.ts index d5da79c40..0a002ea15 100644 --- a/server/api/services/application.ts +++ b/server/api/services/application.ts @@ -15,11 +15,23 @@ import { findAdmin } from "./admin"; import { createTraefikConfig } from "@/server/utils/traefik/application"; import { docker } from "@/server/constants"; import { getAdvancedStats } from "@/server/monitoring/utilts"; +import { validUniqueServerAppName } from "./project"; export type Application = typeof applications.$inferSelect; export const createApplication = async ( input: typeof apiCreateApplication._type, ) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Application with this 'AppName' already exists", + }); + } + } + return await db.transaction(async (tx) => { const newApplication = await tx .insert(applications) diff --git a/server/api/services/mariadb.ts b/server/api/services/mariadb.ts index 7545087f4..1ebd3525d 100644 --- a/server/api/services/mariadb.ts +++ b/server/api/services/mariadb.ts @@ -5,10 +5,22 @@ import { buildMariadb } from "@/server/utils/databases/mariadb"; import { pullImage } from "@/server/utils/docker/utils"; import { TRPCError } from "@trpc/server"; import { eq, getTableColumns } from "drizzle-orm"; +import { validUniqueServerAppName } from "./project"; export type Mariadb = typeof mariadb.$inferSelect; export const createMariadb = async (input: typeof apiCreateMariaDB._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newMariadb = await db .insert(mariadb) .values({ diff --git a/server/api/services/mongo.ts b/server/api/services/mongo.ts index a7605ffec..e6114ef45 100644 --- a/server/api/services/mongo.ts +++ b/server/api/services/mongo.ts @@ -5,10 +5,22 @@ import { buildMongo } from "@/server/utils/databases/mongo"; import { pullImage } from "@/server/utils/docker/utils"; import { TRPCError } from "@trpc/server"; import { eq, getTableColumns } from "drizzle-orm"; +import { validUniqueServerAppName } from "./project"; export type Mongo = typeof mongo.$inferSelect; export const createMongo = async (input: typeof apiCreateMongo._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newMongo = await db .insert(mongo) .values({ diff --git a/server/api/services/mysql.ts b/server/api/services/mysql.ts index b09aadaab..3482968dc 100644 --- a/server/api/services/mysql.ts +++ b/server/api/services/mysql.ts @@ -5,11 +5,22 @@ import { buildMysql } from "@/server/utils/databases/mysql"; import { pullImage } from "@/server/utils/docker/utils"; import { TRPCError } from "@trpc/server"; import { eq, getTableColumns } from "drizzle-orm"; -import { nanoid } from "nanoid"; +import { validUniqueServerAppName } from "./project"; export type MySql = typeof mysql.$inferSelect; export const createMysql = async (input: typeof apiCreateMySql._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newMysql = await db .insert(mysql) .values({ diff --git a/server/api/services/postgres.ts b/server/api/services/postgres.ts index 9575ac517..11ac1085b 100644 --- a/server/api/services/postgres.ts +++ b/server/api/services/postgres.ts @@ -5,10 +5,22 @@ import { buildPostgres } from "@/server/utils/databases/postgres"; import { pullImage } from "@/server/utils/docker/utils"; import { TRPCError } from "@trpc/server"; import { eq, getTableColumns } from "drizzle-orm"; +import { validUniqueServerAppName } from "./project"; export type Postgres = typeof postgres.$inferSelect; export const createPostgres = async (input: typeof apiCreatePostgres._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newPostgres = await db .insert(postgres) .values({ diff --git a/server/api/services/project.ts b/server/api/services/project.ts index 687c67f5b..df75c5c90 100644 --- a/server/api/services/project.ts +++ b/server/api/services/project.ts @@ -1,5 +1,14 @@ import { db } from "@/server/db"; -import { type apiCreateProject, projects } from "@/server/db/schema"; +import { + type apiCreateProject, + applications, + mariadb, + mongo, + mysql, + postgres, + projects, + redis, +} from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { findAdmin } from "./admin"; @@ -73,3 +82,41 @@ export const updateProjectById = async ( return result; }; + +export const validUniqueServerAppName = async (appName: string) => { + const query = await db.query.projects.findMany({ + with: { + applications: { + where: eq(applications.appName, appName), + }, + mariadb: { + where: eq(mariadb.appName, appName), + }, + mongo: { + where: eq(mongo.appName, appName), + }, + mysql: { + where: eq(mysql.appName, appName), + }, + postgres: { + where: eq(postgres.appName, appName), + }, + redis: { + where: eq(redis.appName, appName), + }, + }, + }); + + // Filter out items with non-empty fields + const nonEmptyProjects = query.filter( + (project) => + project.applications.length > 0 || + project.mariadb.length > 0 || + project.mongo.length > 0 || + project.mysql.length > 0 || + project.postgres.length > 0 || + project.redis.length > 0, + ); + + return nonEmptyProjects.length === 0; +}; diff --git a/server/api/services/redis.ts b/server/api/services/redis.ts index e04bf41bf..6137b922f 100644 --- a/server/api/services/redis.ts +++ b/server/api/services/redis.ts @@ -5,11 +5,23 @@ import { buildRedis } from "@/server/utils/databases/redis"; import { pullImage } from "@/server/utils/docker/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import { validUniqueServerAppName } from "./project"; export type Redis = typeof redis.$inferSelect; // https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881 export const createRedis = async (input: typeof apiCreateRedis._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } + const newRedis = await db .insert(redis) .values({ diff --git a/server/db/schema/application.ts b/server/db/schema/application.ts index abdeed53e..2c33907b0 100644 --- a/server/db/schema/application.ts +++ b/server/db/schema/application.ts @@ -128,6 +128,7 @@ const createSchema = createInsertSchema(applications, { export const apiCreateApplication = createSchema.pick({ name: true, + appName: true, description: true, projectId: true, }); diff --git a/server/db/schema/mariadb.ts b/server/db/schema/mariadb.ts index 256dfbfbb..96e65a71e 100644 --- a/server/db/schema/mariadb.ts +++ b/server/db/schema/mariadb.ts @@ -79,6 +79,7 @@ const createSchema = createInsertSchema(mariadb, { export const apiCreateMariaDB = createSchema .pick({ name: true, + appName: true, dockerImage: true, databaseRootPassword: true, projectId: true, diff --git a/server/db/schema/mongo.ts b/server/db/schema/mongo.ts index 7406580e9..bbd94c2c1 100644 --- a/server/db/schema/mongo.ts +++ b/server/db/schema/mongo.ts @@ -73,6 +73,7 @@ const createSchema = createInsertSchema(mongo, { export const apiCreateMongo = createSchema .pick({ name: true, + appName: true, dockerImage: true, projectId: true, description: true, diff --git a/server/db/schema/mysql.ts b/server/db/schema/mysql.ts index 9e0c8c77f..986ab88dd 100644 --- a/server/db/schema/mysql.ts +++ b/server/db/schema/mysql.ts @@ -77,6 +77,7 @@ const createSchema = createInsertSchema(mysql, { export const apiCreateMySql = createSchema .pick({ name: true, + appName: true, dockerImage: true, projectId: true, description: true, diff --git a/server/db/schema/postgres.ts b/server/db/schema/postgres.ts index 7cf0f34dd..9684f4783 100644 --- a/server/db/schema/postgres.ts +++ b/server/db/schema/postgres.ts @@ -74,6 +74,7 @@ const createSchema = createInsertSchema(postgres, { export const apiCreatePostgres = createSchema .pick({ name: true, + appName: true, databaseName: true, databaseUser: true, databasePassword: true, diff --git a/server/db/schema/redis.ts b/server/db/schema/redis.ts index 842fe8093..003bbdd2d 100644 --- a/server/db/schema/redis.ts +++ b/server/db/schema/redis.ts @@ -69,6 +69,7 @@ const createSchema = createInsertSchema(redis, { export const apiCreateRedis = createSchema .pick({ name: true, + appName: true, databasePassword: true, dockerImage: true, projectId: true, From 72c366aa10d00698ab589992172e6fadd7831f86 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:41:05 -0600 Subject: [PATCH 2/9] refactor: slugify and add hash to appName --- server/db/schema/application.ts | 18 ++++++++++++------ server/db/schema/compose.ts | 20 ++++++++++++++------ server/db/schema/mariadb.ts | 8 +++++++- server/db/schema/mongo.ts | 8 +++++++- server/db/schema/mysql.ts | 8 +++++++- server/db/schema/postgres.ts | 8 +++++++- server/db/schema/redis.ts | 9 +++++++-- 7 files changed, 61 insertions(+), 18 deletions(-) diff --git a/server/db/schema/application.ts b/server/db/schema/application.ts index 8f5ec01a8..2b9f71961 100644 --- a/server/db/schema/application.ts +++ b/server/db/schema/application.ts @@ -20,6 +20,7 @@ import { } from "drizzle-orm/pg-core"; import { generateAppName } from "./utils"; import { registry } from "./registry"; +import { generatePassword } from "@/templates/utils"; export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]); @@ -307,12 +308,17 @@ const createSchema = createInsertSchema(applications, { networkSwarm: NetworkSwarmSchema.nullable(), }); -export const apiCreateApplication = createSchema.pick({ - name: true, - appName: true, - description: true, - projectId: true, -}); +export const apiCreateApplication = createSchema + .pick({ + name: true, + appName: true, + description: true, + projectId: true, + }) + .transform((data) => ({ + ...data, + appName: `${data.appName}-${generatePassword(6)}` || generateAppName("app"), + })); export const apiFindOneApplication = createSchema .pick({ diff --git a/server/db/schema/compose.ts b/server/db/schema/compose.ts index f94711e04..bc1e641f6 100644 --- a/server/db/schema/compose.ts +++ b/server/db/schema/compose.ts @@ -8,6 +8,7 @@ import { deployments } from "./deployment"; import { generateAppName } from "./utils"; import { applicationStatus } from "./shared"; import { mounts } from "./mount"; +import { generatePassword } from "@/templates/utils"; export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ "git", @@ -74,12 +75,19 @@ const createSchema = createInsertSchema(compose, { composeType: z.enum(["docker-compose", "stack"]).optional(), }); -export const apiCreateCompose = createSchema.pick({ - name: true, - description: true, - projectId: true, - composeType: true, -}); +export const apiCreateCompose = createSchema + .pick({ + name: true, + description: true, + projectId: true, + composeType: true, + appName: true, + }) + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("compose"), + })); export const apiCreateComposeByTemplate = createSchema .pick({ diff --git a/server/db/schema/mariadb.ts b/server/db/schema/mariadb.ts index 96e65a71e..83ec2898c 100644 --- a/server/db/schema/mariadb.ts +++ b/server/db/schema/mariadb.ts @@ -8,6 +8,7 @@ import { projects } from "./project"; import { backups } from "./backups"; import { mounts } from "./mount"; import { generateAppName } from "./utils"; +import { generatePassword } from "@/templates/utils"; export const mariadb = pgTable("mariadb", { mariadbId: text("mariadbId") @@ -88,7 +89,12 @@ export const apiCreateMariaDB = createSchema databaseUser: true, databasePassword: true, }) - .required(); + .required() + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("mariadb"), + })); export const apiFindOneMariaDB = createSchema .pick({ diff --git a/server/db/schema/mongo.ts b/server/db/schema/mongo.ts index bbd94c2c1..2dd1cbb7b 100644 --- a/server/db/schema/mongo.ts +++ b/server/db/schema/mongo.ts @@ -8,6 +8,7 @@ import { projects } from "./project"; import { backups } from "./backups"; import { mounts } from "./mount"; import { generateAppName } from "./utils"; +import { generatePassword } from "@/templates/utils"; export const mongo = pgTable("mongo", { mongoId: text("mongoId") @@ -80,7 +81,12 @@ export const apiCreateMongo = createSchema databaseUser: true, databasePassword: true, }) - .required(); + .required() + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("postgres"), + })); export const apiFindOneMongo = createSchema .pick({ diff --git a/server/db/schema/mysql.ts b/server/db/schema/mysql.ts index 986ab88dd..0efbf28a1 100644 --- a/server/db/schema/mysql.ts +++ b/server/db/schema/mysql.ts @@ -8,6 +8,7 @@ import { projects } from "./project"; import { backups } from "./backups"; import { mounts } from "./mount"; import { generateAppName } from "./utils"; +import { generatePassword } from "@/templates/utils"; export const mysql = pgTable("mysql", { mysqlId: text("mysqlId") @@ -86,7 +87,12 @@ export const apiCreateMySql = createSchema databasePassword: true, databaseRootPassword: true, }) - .required(); + .required() + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("mysql"), + })); export const apiFindOneMySql = createSchema .pick({ diff --git a/server/db/schema/postgres.ts b/server/db/schema/postgres.ts index 9684f4783..5e9077da1 100644 --- a/server/db/schema/postgres.ts +++ b/server/db/schema/postgres.ts @@ -8,6 +8,7 @@ import { projects } from "./project"; import { backups } from "./backups"; import { mounts } from "./mount"; import { generateAppName } from "./utils"; +import { generatePassword } from "@/templates/utils"; export const postgres = pgTable("postgres", { postgresId: text("postgresId") @@ -82,7 +83,12 @@ export const apiCreatePostgres = createSchema projectId: true, description: true, }) - .required(); + .required() + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("postgres"), + })); export const apiFindOnePostgres = createSchema .pick({ diff --git a/server/db/schema/redis.ts b/server/db/schema/redis.ts index 003bbdd2d..eb9197640 100644 --- a/server/db/schema/redis.ts +++ b/server/db/schema/redis.ts @@ -7,6 +7,7 @@ import { integer, pgTable, text } from "drizzle-orm/pg-core"; import { projects } from "./project"; import { mounts } from "./mount"; import { generateAppName } from "./utils"; +import { generatePassword } from "@/templates/utils"; export const redis = pgTable("redis", { redisId: text("redisId") @@ -75,8 +76,12 @@ export const apiCreateRedis = createSchema projectId: true, description: true, }) - - .required(); + .required() + .transform((data) => ({ + ...data, + appName: + `${data.appName}-${generatePassword(6)}` || generateAppName("redis"), + })); export const apiFindOneRedis = createSchema .pick({ From dd16baf234f8c08a43f586ee1a854e957a3c0348 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:41:38 -0600 Subject: [PATCH 3/9] refactor: use slugify function --- .../dashboard/project/add-application.tsx | 48 ++----------------- components/dashboard/project/add-compose.tsx | 41 ++++++++++++++-- components/dashboard/project/add-database.tsx | 9 ++-- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/components/dashboard/project/add-application.tsx b/components/dashboard/project/add-application.tsx index 712db33b0..ecf2a1afb 100644 --- a/components/dashboard/project/add-application.tsx +++ b/components/dashboard/project/add-application.tsx @@ -27,6 +27,7 @@ import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Textarea } from "@/components/ui/textarea"; +import { slugify } from "@/lib/slug"; const AddTemplateSchema = z.object({ name: z.string().min(1, { @@ -54,6 +55,7 @@ interface Props { export const AddApplication = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); + const slug = slugify(projectName); const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -61,7 +63,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => { const form = useForm({ defaultValues: { name: "", - appName: `${projectName}-`, + appName: `${slug}-`, description: "", }, resolver: zodResolver(AddTemplateSchema), @@ -106,7 +108,6 @@ export const AddApplication = ({ projectId, projectName }: Props) => { {isError && {error?.message}} - { {...field} onChange={(e) => { const val = e.target.value?.trim() || ""; - form.setValue("appName", `${projectName}-${val}`); + form.setValue("appName", `${slug}-${val}`); field.onChange(val); }} /> @@ -165,47 +166,6 @@ export const AddApplication = ({ projectId, projectName }: Props) => { )} /> - - {/* ( - - Build Type - - - - - - - - Dockerfile - - - - - - - Nixpacks - - - - - - - Heroku Buildpacks - - - - - - - )} - /> */} diff --git a/components/dashboard/project/add-compose.tsx b/components/dashboard/project/add-compose.tsx index 97df55ef8..fd769c911 100644 --- a/components/dashboard/project/add-compose.tsx +++ b/components/dashboard/project/add-compose.tsx @@ -34,12 +34,22 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { slugify } from "@/lib/slug"; const AddComposeSchema = z.object({ composeType: z.enum(["docker-compose", "stack"]).optional(), name: z.string().min(1, { message: "Name is required", }), + appName: z + .string() + .min(1, { + message: "App name is required", + }) + .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { + message: + "App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", + }), description: z.string().optional(), }); @@ -47,11 +57,12 @@ type AddCompose = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddCompose = ({ projectId }: Props) => { +export const AddCompose = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); - + const slug = slugify(projectName); const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); @@ -60,6 +71,7 @@ export const AddCompose = ({ projectId }: Props) => { name: "", description: "", composeType: "docker-compose", + appName: `${slug}-`, }, resolver: zodResolver(AddComposeSchema), }); @@ -74,6 +86,7 @@ export const AddCompose = ({ projectId }: Props) => { description: data.description, projectId, composeType: data.composeType, + appName: data.appName, }) .then(async () => { toast.success("Compose Created"); @@ -120,14 +133,34 @@ export const AddCompose = ({ projectId }: Props) => { Name - + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${slug}-${val}`); + field.onChange(val); + }} + /> - )} /> + ( + + AppName + + + + + + )} + /> { const utils = api.useUtils(); const [visible, setVisible] = useState(false); - + const slug = slugify(projectName); const postgresMutation = api.postgres.create.useMutation(); const mongoMutation = api.mongo.create.useMutation(); const redisMutation = api.redis.create.useMutation(); @@ -155,7 +156,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { type: "postgres", dockerImage: "", name: "", - appName: `${projectName}-`, + appName: `${slug}-`, databasePassword: "", description: "", databaseName: "", @@ -341,7 +342,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { {...field} onChange={(e) => { const val = e.target.value?.trim() || ""; - form.setValue("appName", `${projectName}-${val}`); + form.setValue("appName", `${slug}-${val}`); field.onChange(val); }} /> From 295cf50060145ac881f6f86da5455c0407a2870f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:41:46 -0600 Subject: [PATCH 4/9] feat: add slug function --- lib/slug.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/slug.ts diff --git a/lib/slug.ts b/lib/slug.ts new file mode 100644 index 000000000..a4982a0e6 --- /dev/null +++ b/lib/slug.ts @@ -0,0 +1,15 @@ +import slug from "slugify"; + +export const slugify = (text: string | undefined) => { + if (!text) { + return ""; + } + + const cleanedText = text.trim().replace(/[^a-zA-Z0-9\s]/g, ""); + + return slug(cleanedText, { + lower: true, + trim: true, + strict: true, + }); +}; From 909e536f45e650db59af7217fbc86cf67d03a40c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:42:07 -0600 Subject: [PATCH 5/9] refactor: hide enviroment variables when viewing enviroment variables --- .../application/environment/show.tsx | 59 ++++++++++++++++--- .../dashboard/compose/enviroment/show.tsx | 59 ++++++++++++++++--- 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/components/dashboard/application/environment/show.tsx b/components/dashboard/application/environment/show.tsx index 29b536cb2..72f25d2e7 100644 --- a/components/dashboard/application/environment/show.tsx +++ b/components/dashboard/application/environment/show.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Card, CardContent, @@ -19,8 +19,9 @@ import { } from "@/components/ui/form"; import { api } from "@/utils/api"; import { toast } from "sonner"; -import { Textarea } from "@/components/ui/textarea"; +import { Toggle } from "@/components/ui/toggle"; import { CodeEditor } from "@/components/shared/code-editor"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; const addEnvironmentSchema = z.object({ environment: z.string(), @@ -33,6 +34,7 @@ interface Props { } export const ShowEnvironment = ({ applicationId }: Props) => { + const [isEnvVisible, setIsEnvVisible] = useState(true); const { mutateAsync, isLoading } = api.application.saveEnvironment.useMutation(); @@ -72,15 +74,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => { toast.error("Error to add environment"); }); }; + useEffect(() => { + if (isEnvVisible) { + if (data?.env) { + const maskedLines = data.env + .split("\n") + .map((line) => "*".repeat(line.length)) + .join("\n"); + form.reset({ + environment: maskedLines, + }); + } else { + form.reset({ + environment: "", + }); + } + } else { + form.reset({ + environment: data?.env || "", + }); + } + }, [form.reset, data, form, isEnvVisible]); return (
- - Environment Settings - - You can add environment variables to your resource. - + +
+ Environment Settings + + You can add environment variables to your resource. + +
+ + + {isEnvVisible ? ( + + ) : ( + + )} +
@@ -97,6 +134,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
-
diff --git a/components/dashboard/compose/enviroment/show.tsx b/components/dashboard/compose/enviroment/show.tsx index 824e3ce86..c897ac6a0 100644 --- a/components/dashboard/compose/enviroment/show.tsx +++ b/components/dashboard/compose/enviroment/show.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Card, CardContent, @@ -20,6 +20,8 @@ import { import { api } from "@/utils/api"; import { toast } from "sonner"; import { CodeEditor } from "@/components/shared/code-editor"; +import { Toggle } from "@/components/ui/toggle"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; const addEnvironmentSchema = z.object({ environment: z.string(), @@ -32,6 +34,7 @@ interface Props { } export const ShowEnvironmentCompose = ({ composeId }: Props) => { + const [isEnvVisible, setIsEnvVisible] = useState(true); const { mutateAsync, isLoading } = api.compose.update.useMutation(); const { data, refetch } = api.compose.one.useQuery( @@ -71,14 +74,50 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => { }); }; + useEffect(() => { + if (isEnvVisible) { + if (data?.env) { + const maskedLines = data.env + .split("\n") + .map((line) => "*".repeat(line.length)) + .join("\n"); + form.reset({ + environment: maskedLines, + }); + } else { + form.reset({ + environment: "", + }); + } + } else { + form.reset({ + environment: data?.env || "", + }); + } + }, [form.reset, data, form, isEnvVisible]); + return (
- - Environment Settings - - You can add environment variables to your resource. - + +
+ Environment Settings + + You can add environment variables to your resource. + +
+ + + {isEnvVisible ? ( + + ) : ( + + )} +
@@ -95,6 +134,7 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
-
From 83153471b84fb4770df2ed7d21c4176afd82c8ae Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:42:38 -0600 Subject: [PATCH 6/9] feat: add docker compose appName validation --- pages/dashboard/project/[projectId].tsx | 2 +- server/api/routers/compose.ts | 12 +++++++++--- server/api/services/compose.ts | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pages/dashboard/project/[projectId].tsx b/pages/dashboard/project/[projectId].tsx index a7d34a88c..acc4a1913 100644 --- a/pages/dashboard/project/[projectId].tsx +++ b/pages/dashboard/project/[projectId].tsx @@ -215,7 +215,7 @@ const Project = ( projectName={data?.name} /> - + diff --git a/server/api/routers/compose.ts b/server/api/routers/compose.ts index 6da5fe860..66a1327d7 100644 --- a/server/api/routers/compose.ts +++ b/server/api/routers/compose.ts @@ -34,13 +34,18 @@ import { nanoid } from "nanoid"; import { removeDeploymentsByComposeId } from "../services/deployment"; import { removeComposeDirectory } from "@/server/utils/filesystem/directory"; import { createCommand } from "@/server/utils/builders/compose"; -import { loadTemplateModule, readComposeFile } from "@/templates/utils"; +import { + generatePassword, + loadTemplateModule, + readComposeFile, +} from "@/templates/utils"; import { findAdmin } from "../services/admin"; import { TRPCError } from "@trpc/server"; -import { findProjectById, slugifyProjectName } from "../services/project"; +import { findProjectById } from "../services/project"; import { createMount } from "../services/mount"; import type { TemplatesKeys } from "@/templates/types/templates-data.type"; import { templates } from "@/templates/templates"; +import { slugify } from "@/lib/slug"; export const composeRouter = createTRPCRouter({ create: protectedProcedure @@ -229,7 +234,7 @@ export const composeRouter = createTRPCRouter({ const project = await findProjectById(input.projectId); - const projectName = slugifyProjectName(`${project.name}-${input.id}`); + const projectName = slugify(`${project.name}-${input.id}`); const { envs, mounts } = generate({ serverIp: admin.serverIp, projectName: projectName, @@ -241,6 +246,7 @@ export const composeRouter = createTRPCRouter({ env: envs.join("\n"), name: input.id, sourceType: "raw", + appName: `${projectName}-${generatePassword(6)}`, }); if (ctx.user.rol === "user") { diff --git a/server/api/services/compose.ts b/server/api/services/compose.ts index 8f519e8a8..3a7739189 100644 --- a/server/api/services/compose.ts +++ b/server/api/services/compose.ts @@ -13,10 +13,21 @@ import { join } from "node:path"; import { COMPOSE_PATH } from "@/server/constants"; import { cloneGithubRepository } from "@/server/utils/providers/github"; import { cloneGitRepository } from "@/server/utils/providers/git"; +import { validUniqueServerAppName } from "./project"; export type Compose = typeof compose.$inferSelect; export const createCompose = async (input: typeof apiCreateCompose._type) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } const newDestination = await db .insert(compose) .values({ @@ -39,6 +50,16 @@ export const createCompose = async (input: typeof apiCreateCompose._type) => { export const createComposeByTemplate = async ( input: typeof compose.$inferInsert, ) => { + if (input.appName) { + const valid = await validUniqueServerAppName(input.appName); + + if (!valid) { + throw new TRPCError({ + code: "CONFLICT", + message: "Service with this 'AppName' already exists", + }); + } + } const newDestination = await db .insert(compose) .values({ From b9ab4a4d1a64483a25f475c1018431c65111f990 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:42:50 -0600 Subject: [PATCH 7/9] refactor: remove console log --- server/api/services/docker.ts | 16 ++------ server/api/services/project.ts | 9 ----- server/utils/builders/index.ts | 1 - server/utils/providers/docker.ts | 68 ++++++++++++++++---------------- 4 files changed, 38 insertions(+), 56 deletions(-) diff --git a/server/api/services/docker.ts b/server/api/services/docker.ts index 57f60c360..8c951e8d4 100644 --- a/server/api/services/docker.ts +++ b/server/api/services/docker.ts @@ -46,9 +46,7 @@ export const getContainers = async () => { .filter((container) => !container.name.includes("dokploy")); return containers; - } catch (error) { - console.error(`Execution error: ${error}`); - } + } catch (error) {} }; export const getConfig = async (containerId: string) => { @@ -65,9 +63,7 @@ export const getConfig = async (containerId: string) => { const config = JSON.parse(stdout); return config; - } catch (error) { - console.error(`Execution error: ${error}`); - } + } catch (error) {} }; export const getContainersByAppNameMatch = async (appName: string) => { @@ -103,9 +99,7 @@ export const getContainersByAppNameMatch = async (appName: string) => { }); return containers || []; - } catch (error) { - console.error(`Execution error: ${error}`); - } + } catch (error) {} return []; }; @@ -144,9 +138,7 @@ export const getContainersByAppLabel = async (appName: string) => { }); return containers || []; - } catch (error) { - console.error(`Execution error: ${error}`); - } + } catch (error) {} return []; }; diff --git a/server/api/services/project.ts b/server/api/services/project.ts index 2ed1244c3..56aecce5f 100644 --- a/server/api/services/project.ts +++ b/server/api/services/project.ts @@ -121,12 +121,3 @@ export const validUniqueServerAppName = async (appName: string) => { return nonEmptyProjects.length === 0; }; -export const slugifyProjectName = (projectName: string): string => { - return projectName - .toLowerCase() - .replace(/[0-9]/g, "") - .replace(/[^a-z\s-]/g, "") - .replace(/\s+/g, "-") - .replace(/-+/g, "-") - .replace(/^-+|-+$/g, ""); -}; diff --git a/server/utils/builders/index.ts b/server/utils/builders/index.ts index e67ad9be6..ce8cad39a 100644 --- a/server/utils/builders/index.ts +++ b/server/utils/builders/index.ts @@ -148,7 +148,6 @@ export const mechanizeDockerContainer = async ( }, }); } catch (error) { - console.log(error); await docker.createService(settings); } }; diff --git a/server/utils/providers/docker.ts b/server/utils/providers/docker.ts index 997648d12..c77a6721b 100644 --- a/server/utils/providers/docker.ts +++ b/server/utils/providers/docker.ts @@ -3,47 +3,47 @@ import { type ApplicationNested, mechanizeDockerContainer } from "../builders"; import { pullImage } from "../docker/utils"; interface RegistryAuth { - username: string; - password: string; - serveraddress: string; + username: string; + password: string; + serveraddress: string; } export const buildDocker = async ( - application: ApplicationNested, - logPath: string, + application: ApplicationNested, + logPath: string, ): Promise => { - const { buildType, dockerImage, username, password } = application; - const authConfig: Partial = { - username: username || "", - password: password || "", - }; + const { buildType, dockerImage, username, password } = application; + const authConfig: Partial = { + username: username || "", + password: password || "", + }; - const writeStream = createWriteStream(logPath, { flags: "a" }); + const writeStream = createWriteStream(logPath, { flags: "a" }); - writeStream.write(`\nBuild ${buildType}\n`); + writeStream.write(`\nBuild ${buildType}\n`); - writeStream.write(`Pulling ${dockerImage}: ✅\n`); + writeStream.write(`Pulling ${dockerImage}: ✅\n`); - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } + try { + if (!dockerImage) { + throw new Error("Docker image not found"); + } - await pullImage( - dockerImage, - (data) => { - if (writeStream.writable) { - writeStream.write(`${data.status}\n`); - } - }, - authConfig, - ); - await mechanizeDockerContainer(application); - writeStream.write("\nDocker Deployed: ✅\n"); - } catch (error) { - writeStream.write(`ERROR: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } + await pullImage( + dockerImage, + (data) => { + if (writeStream.writable) { + writeStream.write(`${data.status}\n`); + } + }, + authConfig, + ); + await mechanizeDockerContainer(application); + writeStream.write("\nDocker Deployed: ✅\n"); + } catch (error) { + writeStream.write(`ERROR: ${error}: ❌`); + throw error; + } finally { + writeStream.end(); + } }; From 993d6b52f2b800267abe121db9a1bdca19ab7b65 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:43:01 -0600 Subject: [PATCH 8/9] refactor: remove comments --- utils/api.ts | 67 ++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/utils/api.ts b/utils/api.ts index 98806f937..625f80087 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -11,50 +11,39 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import superjson from "superjson"; const getBaseUrl = () => { - if (typeof window !== "undefined") return ""; // browser should use relative url - return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost + if (typeof window !== "undefined") return ""; // browser should use relative url + return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost }; /** A set of type-safe react-query hooks for your tRPC API. */ export const api = createTRPCNext({ - config() { - return { - /** - * Transformer used for data de-serialization from the server. - * - * @see https://trpc.io/docs/data-transformers - */ - transformer: superjson, + config() { + return { + /** + * Transformer used for data de-serialization from the server. + * + * @see https://trpc.io/docs/data-transformers + */ + transformer: superjson, - /** - * Links used to determine request flow from client to server. - * - * @see https://trpc.io/docs/links - */ - links: [ - httpBatchLink({ - url: `${getBaseUrl()}/api/trpc`, - }), - // createWSClient({ - // url: `ws://localhost:3000`, - // }), - // loggerLink({ - // enabled: (opts) => - // process.env.NODE_ENV === "development" || - // (opts.direction === "down" && opts.result instanceof Error), - // }), - // httpBatchLink({ - // url: `${getBaseUrl()}/api/trpc`, - // }), - ], - }; - }, - /** - * Whether tRPC should await queries when server rendering pages. - * - * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false - */ - ssr: false, + /** + * Links used to determine request flow from client to server. + * + * @see https://trpc.io/docs/links + */ + links: [ + httpBatchLink({ + url: `${getBaseUrl()}/api/trpc`, + }), + ], + }; + }, + /** + * Whether tRPC should await queries when server rendering pages. + * + * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false + */ + ssr: false, }); /** From 0ef9b1427bd077a67ad00c7266ab7d91d767a6e0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:43:06 -0600 Subject: [PATCH 9/9] chore: add slugify --- package.json | 10 +++++----- pnpm-lock.yaml | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f85c0af6e..eafbeb914 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,11 @@ "test": "vitest --config __test__/vitest.config.ts" }, "dependencies": { - "@codemirror/language":"^6.10.1", "@aws-sdk/client-s3": "3.515.0", - "@codemirror/legacy-modes":"6.4.0", "@codemirror/lang-json": "^6.0.1", "@codemirror/lang-yaml": "^6.1.1", + "@codemirror/language": "^6.10.1", + "@codemirror/legacy-modes": "6.4.0", "@faker-js/faker": "^8.4.1", "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-drizzle": "1.0.7", @@ -76,6 +76,7 @@ "clsx": "^2.1.0", "cmdk": "^0.2.0", "copy-to-clipboard": "^3.3.3", + "copy-webpack-plugin": "^12.0.2", "date-fns": "3.6.0", "dockerode": "4.0.2", "dockerode-compose": "^1.4.0", @@ -105,6 +106,7 @@ "react-dom": "18.2.0", "react-hook-form": "^7.49.3", "recharts": "^2.12.3", + "slugify": "^1.6.6", "sonner": "^1.4.0", "superjson": "^2.2.1", "tailwind-merge": "^2.2.0", @@ -113,9 +115,7 @@ "use-resize-observer": "9.1.0", "ws": "8.16.0", "xterm-addon-fit": "^0.8.0", - "zod": "^3.23.4", - "copy-webpack-plugin": "^12.0.2" - + "zod": "^3.23.4" }, "devDependencies": { "@biomejs/biome": "1.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c44c7cff..f7af8ad9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -230,6 +230,9 @@ dependencies: recharts: specifier: ^2.12.3 version: 2.12.3(react-dom@18.2.0)(react@18.2.0) + slugify: + specifier: ^1.6.6 + version: 1.6.6 sonner: specifier: ^1.4.0 version: 1.4.3(react-dom@18.2.0)(react@18.2.0) @@ -8015,6 +8018,11 @@ packages: engines: {node: '>=14.16'} dev: false + /slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} + engines: {node: '>=8.0.0'} + dev: false + /sonner@1.4.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw==} peerDependencies: