feat(schema): enhance appName validation across database schemas with regex and message

This commit is contained in:
Mauricio Siu
2026-01-31 03:01:49 -06:00
parent 20226a300c
commit 960892fd8d
8 changed files with 110 additions and 77 deletions
+7 -2
View File
@@ -47,7 +47,7 @@ import {
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { sshKeys } from "./ssh-key"; import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const sourceType = pgEnum("sourceType", [ export const sourceType = pgEnum("sourceType", [
"docker", "docker",
"git", "git",
@@ -287,7 +287,12 @@ export const applicationsRelations = relations(
); );
const createSchema = createInsertSchema(applications, { const createSchema = createInsertSchema(applications, {
appName: z.string(), appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
createdAt: z.string(), createdAt: z.string(),
applicationId: z.string(), applicationId: z.string(),
autoDeploy: z.boolean(), autoDeploy: z.boolean(),
+7 -1
View File
@@ -16,7 +16,7 @@ import { schedules } from "./schedule";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus, triggerType } from "./shared"; import { applicationStatus, triggerType } from "./shared";
import { sshKeys } from "./ssh-key"; import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git", "git",
"github", "github",
@@ -147,6 +147,12 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
const createSchema = createInsertSchema(compose, { const createSchema = createInsertSchema(compose, {
name: z.string().min(1), name: z.string().min(1),
appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
description: z.string(), description: z.string(),
env: z.string().optional(), env: z.string().optional(),
composeFile: z.string().optional(), composeFile: z.string().optional(),
+19 -16
View File
@@ -26,7 +26,7 @@ import {
type UpdateConfigSwarm, type UpdateConfigSwarm,
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const mariadb = pgTable("mariadb", { export const mariadb = pgTable("mariadb", {
mariadbId: text("mariadbId") mariadbId: text("mariadbId")
@@ -96,7 +96,12 @@ export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
const createSchema = createInsertSchema(mariadb, { const createSchema = createInsertSchema(mariadb, {
mariadbId: z.string(), mariadbId: z.string(),
name: z.string().min(1), name: z.string().min(1),
appName: z.string().min(1), appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
createdAt: z.string(), createdAt: z.string(),
databaseName: z.string().min(1), databaseName: z.string().min(1),
databaseUser: z.string().min(1), databaseUser: z.string().min(1),
@@ -138,20 +143,18 @@ const createSchema = createInsertSchema(mariadb, {
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
}); });
export const apiCreateMariaDB = createSchema export const apiCreateMariaDB = createSchema.pick({
.pick({ name: true,
name: true, appName: true,
appName: true, dockerImage: true,
dockerImage: true, databaseRootPassword: true,
databaseRootPassword: true, environmentId: true,
environmentId: true, description: true,
description: true, databaseName: true,
databaseName: true, databaseUser: true,
databaseUser: true, databasePassword: true,
databasePassword: true, serverId: true,
serverId: true, });
})
.required();
export const apiFindOneMariaDB = createSchema export const apiFindOneMariaDB = createSchema
.pick({ .pick({
+18 -15
View File
@@ -33,7 +33,7 @@ import {
type UpdateConfigSwarm, type UpdateConfigSwarm,
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const mongo = pgTable("mongo", { export const mongo = pgTable("mongo", {
mongoId: text("mongoId") mongoId: text("mongoId")
@@ -98,7 +98,12 @@ export const mongoRelations = relations(mongo, ({ one, many }) => ({
})); }));
const createSchema = createInsertSchema(mongo, { const createSchema = createInsertSchema(mongo, {
appName: z.string().min(1), appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
createdAt: z.string(), createdAt: z.string(),
mongoId: z.string(), mongoId: z.string(),
name: z.string().min(1), name: z.string().min(1),
@@ -135,19 +140,17 @@ const createSchema = createInsertSchema(mongo, {
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
}); });
export const apiCreateMongo = createSchema export const apiCreateMongo = createSchema.pick({
.pick({ name: true,
name: true, appName: true,
appName: true, dockerImage: true,
dockerImage: true, environmentId: true,
environmentId: true, description: true,
description: true, databaseUser: true,
databaseUser: true, databasePassword: true,
databasePassword: true, serverId: true,
serverId: true, replicaSets: true,
replicaSets: true, });
})
.required();
export const apiFindOneMongo = createSchema export const apiFindOneMongo = createSchema
.pick({ .pick({
+19 -16
View File
@@ -26,7 +26,7 @@ import {
type UpdateConfigSwarm, type UpdateConfigSwarm,
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const mysql = pgTable("mysql", { export const mysql = pgTable("mysql", {
mysqlId: text("mysqlId") mysqlId: text("mysqlId")
@@ -93,7 +93,12 @@ export const mysqlRelations = relations(mysql, ({ one, many }) => ({
const createSchema = createInsertSchema(mysql, { const createSchema = createInsertSchema(mysql, {
mysqlId: z.string(), mysqlId: z.string(),
appName: z.string().min(1), appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
createdAt: z.string(), createdAt: z.string(),
name: z.string().min(1), name: z.string().min(1),
databaseName: z.string().min(1), databaseName: z.string().min(1),
@@ -135,20 +140,18 @@ const createSchema = createInsertSchema(mysql, {
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
}); });
export const apiCreateMySql = createSchema export const apiCreateMySql = createSchema.pick({
.pick({ name: true,
name: true, appName: true,
appName: true, dockerImage: true,
dockerImage: true, environmentId: true,
environmentId: true, description: true,
description: true, databaseName: true,
databaseName: true, databaseUser: true,
databaseUser: true, databasePassword: true,
databasePassword: true, databaseRootPassword: true,
databaseRootPassword: true, serverId: true,
serverId: true, });
})
.required();
export const apiFindOneMySql = createSchema export const apiFindOneMySql = createSchema
.pick({ .pick({
+18 -14
View File
@@ -26,7 +26,7 @@ import {
type UpdateConfigSwarm, type UpdateConfigSwarm,
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const postgres = pgTable("postgres", { export const postgres = pgTable("postgres", {
postgresId: text("postgresId") postgresId: text("postgresId")
@@ -94,6 +94,12 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
const createSchema = createInsertSchema(postgres, { const createSchema = createInsertSchema(postgres, {
postgresId: z.string(), postgresId: z.string(),
name: z.string().min(1), name: z.string().min(1),
appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
databasePassword: z databasePassword: z
.string() .string()
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
@@ -128,19 +134,17 @@ const createSchema = createInsertSchema(postgres, {
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
}); });
export const apiCreatePostgres = createSchema export const apiCreatePostgres = createSchema.pick({
.pick({ name: true,
name: true, appName: true,
appName: true, databaseName: true,
databaseName: true, databaseUser: true,
databaseUser: true, databasePassword: true,
databasePassword: true, dockerImage: true,
dockerImage: true, environmentId: true,
environmentId: true, description: true,
description: true, serverId: true,
serverId: true, });
})
.required();
export const apiFindOnePostgres = createSchema export const apiFindOnePostgres = createSchema
.pick({ .pick({
+16 -13
View File
@@ -25,7 +25,7 @@ import {
type UpdateConfigSwarm, type UpdateConfigSwarm,
UpdateConfigSwarmSchema, UpdateConfigSwarmSchema,
} from "./shared"; } from "./shared";
import { generateAppName } from "./utils"; import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
export const redis = pgTable("redis", { export const redis = pgTable("redis", {
redisId: text("redisId") redisId: text("redisId")
@@ -88,7 +88,12 @@ export const redisRelations = relations(redis, ({ one, many }) => ({
const createSchema = createInsertSchema(redis, { const createSchema = createInsertSchema(redis, {
redisId: z.string(), redisId: z.string(),
appName: z.string().min(1), appName: z
.string()
.min(1)
.max(63)
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
.optional(),
createdAt: z.string(), createdAt: z.string(),
name: z.string().min(1), name: z.string().min(1),
databasePassword: z.string(), databasePassword: z.string(),
@@ -117,17 +122,15 @@ const createSchema = createInsertSchema(redis, {
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
}); });
export const apiCreateRedis = createSchema export const apiCreateRedis = createSchema.pick({
.pick({ name: true,
name: true, appName: true,
appName: true, databasePassword: true,
databasePassword: true, dockerImage: true,
dockerImage: true, environmentId: true,
environmentId: true, description: true,
description: true, serverId: true,
serverId: true, });
})
.required();
export const apiFindOneRedis = createSchema export const apiFindOneRedis = createSchema
.pick({ .pick({
+6
View File
@@ -6,6 +6,12 @@ const alphabet = "abcdefghijklmnopqrstuvwxyz123456789";
const customNanoid = customAlphabet(alphabet, 6); const customNanoid = customAlphabet(alphabet, 6);
/** App name: letters, numbers, dots, underscores, hyphens only (no spaces). Safe for shell/Docker. */
export const APP_NAME_REGEX = /^[a-zA-Z0-9._-]+$/;
export const APP_NAME_MESSAGE =
"App name can only contain letters, numbers, dots, underscores and hyphens";
export const generateAppName = (type: string) => { export const generateAppName = (type: string) => {
const verb = faker.hacker.verb().replace(/ /g, "-"); const verb = faker.hacker.verb().replace(/ /g, "-");
const adjective = faker.hacker.adjective().replace(/ /g, "-"); const adjective = faker.hacker.adjective().replace(/ /g, "-");