From 7aba5a2b3d5e12e638c834f161e0721bbc1fff94 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Tue, 10 Sep 2024 13:43:31 +0530 Subject: [PATCH 01/11] fix(API):Update the functionality by which slugs are generated for entities --- apps/api/src/common/slug-generator.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 2402c485..6c4151e7 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -8,7 +8,7 @@ import { Workspace } from '@prisma/client' * @param name The name of the entity. * @returns A unique slug for the given entity. */ -const generateSlug = (name: string): string => { +const generateSlug = (name: string,counter:number): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -20,7 +20,7 @@ const generateSlug = (name: string): string => { // Append the name with 5 alphanumeric characters const slug = - alphanumericName + '-' + Math.random().toString(36).substring(2, 7) + alphanumericName + '-' + counter.toString(36) return slug } @@ -102,46 +102,55 @@ export default async function generateEntitySlug( | 'API_KEY', prisma: PrismaService ): Promise { + let counter=0 while (true) { - const slug = generateSlug(name) + const slug = generateSlug(name,counter) switch (entityType) { case 'WORKSPACE_ROLE': if (await checkWorkspaceRoleSlugExists(slug, prisma)) { + counter++ continue } return slug case 'WORKSPACE': if (await checkWorkspaceSlugExists(slug, prisma)) { + counter++ continue } return slug case 'PROJECT': if (await checkProjectSlugExists(slug, prisma)) { + counter++ continue } return slug case 'VARIABLE': if (await checkVariableSlugExists(slug, prisma)) { + counter++ continue } return slug case 'SECRET': if (await checkSecretSlugExists(slug, prisma)) { + counter++ continue } return slug case 'INTEGRATION': if (await checkIntegrationSlugExists(slug, prisma)) { + counter++ continue } return slug case 'ENVIRONMENT': if (await checkEnvironmentSlugExists(slug, prisma)) { + counter++ continue } return slug case 'API_KEY': if (await checkApiKeySlugExists(slug, prisma)) { + counter++ continue } return slug From 2587d65b7178468e4f8f0b038d81348062b06e25 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Tue, 10 Sep 2024 16:56:13 +0530 Subject: [PATCH 02/11] Fixed char increment and optimized slug check --- apps/api/src/common/slug-generator.ts | 188 +++++++++++--------------- 1 file changed, 76 insertions(+), 112 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 6c4151e7..a2503f79 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,14 +1,12 @@ import { PrismaService } from '@/prisma/prisma.service' -import { Workspace } from '@prisma/client' /** - * Generates a unique slug for the given name. It keeps generating slugs until it finds - * one that does not exist in the database. + * Generates the base slug from the given name. * * @param name The name of the entity. - * @returns A unique slug for the given entity. + * @returns The base slug. */ -const generateSlug = (name: string,counter:number): string => { +const generateBaseSlug = (name: string): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -18,75 +16,64 @@ const generateSlug = (name: string,counter:number): string => { // Replace all non-alphanumeric characters with hyphens const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '-') - // Append the name with 5 alphanumeric characters - const slug = - alphanumericName + '-' + counter.toString(36) - return slug + return alphanumericName } -const checkWorkspaceRoleSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.workspaceRole.count({ where: { slug } })) > 0 -} +const convertEntityTypeToCamelCase = (entityType: string): string => { + return entityType + .toLowerCase() + .split('_') + .map((word, index) => + index === 0 + ? word // First word stays lowercase + : word.charAt(0).toUpperCase() + word.slice(1) // Capitalize the first letter of subsequent words + ) + .join(''); +}; -const checkWorkspaceSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.workspace.count({ where: { slug } })) > 0 -} +const incrementSlugSuffix = (suffix: string): string => { + const charset = '0123456789abcdefghijklmnopqrstuvwxyz'; -const checkProjectSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.project.count({ where: { slug } })) > 0 -} + if (!suffix) { + return '0'; + } -const checkVariableSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.variable.count({ where: { slug } })) > 0 -} + let result = ''; + let carry = true; -const checkSecretSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.secret.count({ where: { slug } })) > 0 -} + for (let i = suffix.length - 1; i >= 0; i--) { + if (carry) { + const currentChar = suffix[i]; + const index = charset.indexOf(currentChar); -const checkIntegrationSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.integration.count({ where: { slug } })) > 0 -} + if (index === -1) { + throw new Error(`Invalid character in slug suffix: ${currentChar}`); + } + const nextIndex = (index + 1) % charset.length; + result = charset[nextIndex] + result; -const checkEnvironmentSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.environment.count({ where: { slug } })) > 0 -} + // Carry over if we wrapped around to '0' + carry = nextIndex === 0; + } else { + // No carry, just append the remaining part of the suffix + result = suffix[i] + result; + } + } -const checkApiKeySlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.apiKey.count({ where: { slug } })) > 0 -} + if (carry) { + result = '0' + result; + } + + return result; +}; /** - * Generates a unique slug for the given entity type and name. It keeps - * generating slugs until it finds one that does not exist in the database. + * Generates a unique slug for the given entity type and name. + * It queries existing slugs, determines the highest suffix, and generates a new unique slug. * * @param name The name of the entity. * @param entityType The type of the entity. - * @param prisma The Prisma client to use to check the existence of the slug. + * @param prisma The Prisma client to use. * @returns A unique slug for the given entity. */ export default async function generateEntitySlug( @@ -102,58 +89,35 @@ export default async function generateEntitySlug( | 'API_KEY', prisma: PrismaService ): Promise { - let counter=0 - while (true) { - const slug = generateSlug(name,counter) - switch (entityType) { - case 'WORKSPACE_ROLE': - if (await checkWorkspaceRoleSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'WORKSPACE': - if (await checkWorkspaceSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'PROJECT': - if (await checkProjectSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'VARIABLE': - if (await checkVariableSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'SECRET': - if (await checkSecretSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'INTEGRATION': - if (await checkIntegrationSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'ENVIRONMENT': - if (await checkEnvironmentSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'API_KEY': - if (await checkApiKeySlugExists(slug, prisma)) { - counter++ - continue - } - return slug + const baseSlug = generateBaseSlug(name) + + const existingSlugs = await prisma[convertEntityTypeToCamelCase(entityType)].findMany({ + where: { + slug: { + startsWith: baseSlug + } + }, + orderBy: { + slug: 'desc' + } + }); + + let highestSuffix = ''; + + if (existingSlugs.length > 0) { + for (const item of existingSlugs) { + const slug = item.slug; + const suffix = slug.substring(baseSlug.length + 1); + + if (suffix > highestSuffix) { + highestSuffix = suffix; + } } } + + // Increment the highest suffix to generate the new unique slug + const newSuffix = incrementSlugSuffix(highestSuffix); + const uniqueSlug = `${baseSlug}-${newSuffix}`; + + return uniqueSlug; } From 5b637a67d9d3aa6f3ba0070286681d4ef6f2e395 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Tue, 10 Sep 2024 13:43:31 +0530 Subject: [PATCH 03/11] fix(API):Update the functionality by which slugs are generated for entities --- apps/api/src/common/slug-generator.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 2402c485..6c4151e7 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -8,7 +8,7 @@ import { Workspace } from '@prisma/client' * @param name The name of the entity. * @returns A unique slug for the given entity. */ -const generateSlug = (name: string): string => { +const generateSlug = (name: string,counter:number): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -20,7 +20,7 @@ const generateSlug = (name: string): string => { // Append the name with 5 alphanumeric characters const slug = - alphanumericName + '-' + Math.random().toString(36).substring(2, 7) + alphanumericName + '-' + counter.toString(36) return slug } @@ -102,46 +102,55 @@ export default async function generateEntitySlug( | 'API_KEY', prisma: PrismaService ): Promise { + let counter=0 while (true) { - const slug = generateSlug(name) + const slug = generateSlug(name,counter) switch (entityType) { case 'WORKSPACE_ROLE': if (await checkWorkspaceRoleSlugExists(slug, prisma)) { + counter++ continue } return slug case 'WORKSPACE': if (await checkWorkspaceSlugExists(slug, prisma)) { + counter++ continue } return slug case 'PROJECT': if (await checkProjectSlugExists(slug, prisma)) { + counter++ continue } return slug case 'VARIABLE': if (await checkVariableSlugExists(slug, prisma)) { + counter++ continue } return slug case 'SECRET': if (await checkSecretSlugExists(slug, prisma)) { + counter++ continue } return slug case 'INTEGRATION': if (await checkIntegrationSlugExists(slug, prisma)) { + counter++ continue } return slug case 'ENVIRONMENT': if (await checkEnvironmentSlugExists(slug, prisma)) { + counter++ continue } return slug case 'API_KEY': if (await checkApiKeySlugExists(slug, prisma)) { + counter++ continue } return slug From 25178d1bc659eefda30b6ca7df6457df25cc52ab Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Tue, 10 Sep 2024 16:56:13 +0530 Subject: [PATCH 04/11] Fixed char increment and optimized slug check --- apps/api/src/common/slug-generator.ts | 188 +++++++++++--------------- 1 file changed, 76 insertions(+), 112 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 6c4151e7..a2503f79 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,14 +1,12 @@ import { PrismaService } from '@/prisma/prisma.service' -import { Workspace } from '@prisma/client' /** - * Generates a unique slug for the given name. It keeps generating slugs until it finds - * one that does not exist in the database. + * Generates the base slug from the given name. * * @param name The name of the entity. - * @returns A unique slug for the given entity. + * @returns The base slug. */ -const generateSlug = (name: string,counter:number): string => { +const generateBaseSlug = (name: string): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -18,75 +16,64 @@ const generateSlug = (name: string,counter:number): string => { // Replace all non-alphanumeric characters with hyphens const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '-') - // Append the name with 5 alphanumeric characters - const slug = - alphanumericName + '-' + counter.toString(36) - return slug + return alphanumericName } -const checkWorkspaceRoleSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.workspaceRole.count({ where: { slug } })) > 0 -} +const convertEntityTypeToCamelCase = (entityType: string): string => { + return entityType + .toLowerCase() + .split('_') + .map((word, index) => + index === 0 + ? word // First word stays lowercase + : word.charAt(0).toUpperCase() + word.slice(1) // Capitalize the first letter of subsequent words + ) + .join(''); +}; -const checkWorkspaceSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.workspace.count({ where: { slug } })) > 0 -} +const incrementSlugSuffix = (suffix: string): string => { + const charset = '0123456789abcdefghijklmnopqrstuvwxyz'; -const checkProjectSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.project.count({ where: { slug } })) > 0 -} + if (!suffix) { + return '0'; + } -const checkVariableSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.variable.count({ where: { slug } })) > 0 -} + let result = ''; + let carry = true; -const checkSecretSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.secret.count({ where: { slug } })) > 0 -} + for (let i = suffix.length - 1; i >= 0; i--) { + if (carry) { + const currentChar = suffix[i]; + const index = charset.indexOf(currentChar); -const checkIntegrationSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.integration.count({ where: { slug } })) > 0 -} + if (index === -1) { + throw new Error(`Invalid character in slug suffix: ${currentChar}`); + } + const nextIndex = (index + 1) % charset.length; + result = charset[nextIndex] + result; -const checkEnvironmentSlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.environment.count({ where: { slug } })) > 0 -} + // Carry over if we wrapped around to '0' + carry = nextIndex === 0; + } else { + // No carry, just append the remaining part of the suffix + result = suffix[i] + result; + } + } -const checkApiKeySlugExists = async ( - slug: Workspace['slug'], - prisma: PrismaService -): Promise => { - return (await prisma.apiKey.count({ where: { slug } })) > 0 -} + if (carry) { + result = '0' + result; + } + + return result; +}; /** - * Generates a unique slug for the given entity type and name. It keeps - * generating slugs until it finds one that does not exist in the database. + * Generates a unique slug for the given entity type and name. + * It queries existing slugs, determines the highest suffix, and generates a new unique slug. * * @param name The name of the entity. * @param entityType The type of the entity. - * @param prisma The Prisma client to use to check the existence of the slug. + * @param prisma The Prisma client to use. * @returns A unique slug for the given entity. */ export default async function generateEntitySlug( @@ -102,58 +89,35 @@ export default async function generateEntitySlug( | 'API_KEY', prisma: PrismaService ): Promise { - let counter=0 - while (true) { - const slug = generateSlug(name,counter) - switch (entityType) { - case 'WORKSPACE_ROLE': - if (await checkWorkspaceRoleSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'WORKSPACE': - if (await checkWorkspaceSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'PROJECT': - if (await checkProjectSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'VARIABLE': - if (await checkVariableSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'SECRET': - if (await checkSecretSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'INTEGRATION': - if (await checkIntegrationSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'ENVIRONMENT': - if (await checkEnvironmentSlugExists(slug, prisma)) { - counter++ - continue - } - return slug - case 'API_KEY': - if (await checkApiKeySlugExists(slug, prisma)) { - counter++ - continue - } - return slug + const baseSlug = generateBaseSlug(name) + + const existingSlugs = await prisma[convertEntityTypeToCamelCase(entityType)].findMany({ + where: { + slug: { + startsWith: baseSlug + } + }, + orderBy: { + slug: 'desc' + } + }); + + let highestSuffix = ''; + + if (existingSlugs.length > 0) { + for (const item of existingSlugs) { + const slug = item.slug; + const suffix = slug.substring(baseSlug.length + 1); + + if (suffix > highestSuffix) { + highestSuffix = suffix; + } } } + + // Increment the highest suffix to generate the new unique slug + const newSuffix = incrementSlugSuffix(highestSuffix); + const uniqueSlug = `${baseSlug}-${newSuffix}`; + + return uniqueSlug; } From 8f8faf994810418a5975e3512d2a384edf47a2e3 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Thu, 12 Sep 2024 13:45:54 +0530 Subject: [PATCH 05/11] fix(API): Updated slug-generator.ts --- apps/api/src/common/slug-generator.ts | 288 +++++++++++++++++++------- 1 file changed, 213 insertions(+), 75 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index a2503f79..45bffe37 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,12 +1,56 @@ import { PrismaService } from '@/prisma/prisma.service' +import { Workspace } from '@prisma/client' + +const incrementSlugSuffix = (foundSlug: string, baseSlug: string): string => { + const charset = '0123456789abcdefghijklmnopqrstuvwxyz' + + let suffix = '' + + if (!foundSlug) { + suffix = foundSlug.substring(baseSlug.length + 1) + } + + if (!suffix) { + return `${baseSlug}-0` + } + + let result = '' + let carry = true + + for (let i = suffix.length - 1; i >= 0; i--) { + if (carry) { + const currentChar = suffix[i] + const index = charset.indexOf(currentChar) + + if (index === -1) { + throw new Error(`Invalid character in slug suffix: ${currentChar}`) + } + const nextIndex = (index + 1) % charset.length + result = charset[nextIndex] + result + + // Carry over if we wrapped around to '0' + carry = nextIndex === 0 + } else { + // No carry, just append the remaining part of the suffix + result = suffix[i] + result + } + } + + if (carry) { + result = '0' + result + } + + return `${baseSlug}-${result}` +} /** - * Generates the base slug from the given name. + * Generates a unique slug for the given name. It keeps generating slugs until it finds + * one that does not exist in the database. * * @param name The name of the entity. - * @returns The base slug. + * @returns A unique slug for the given entity. */ -const generateBaseSlug = (name: string): string => { +const generateSlugName = (name: string): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -19,61 +63,157 @@ const generateBaseSlug = (name: string): string => { return alphanumericName } -const convertEntityTypeToCamelCase = (entityType: string): string => { - return entityType - .toLowerCase() - .split('_') - .map((word, index) => - index === 0 - ? word // First word stays lowercase - : word.charAt(0).toUpperCase() + word.slice(1) // Capitalize the first letter of subsequent words - ) - .join(''); -}; - -const incrementSlugSuffix = (suffix: string): string => { - const charset = '0123456789abcdefghijklmnopqrstuvwxyz'; +const getWorkspaceRoleIfSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.workspaceRole.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - if (!suffix) { - return '0'; - } +const getWorkspaceSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.workspace.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - let result = ''; - let carry = true; +const getProjectSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.project.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - for (let i = suffix.length - 1; i >= 0; i--) { - if (carry) { - const currentChar = suffix[i]; - const index = charset.indexOf(currentChar); +const getVariableSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.variable.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - if (index === -1) { - throw new Error(`Invalid character in slug suffix: ${currentChar}`); +const getSecretSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.secret.findMany({ + where: { + slug: { + startsWith: `${slug}-` } - const nextIndex = (index + 1) % charset.length; - result = charset[nextIndex] + result; + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - // Carry over if we wrapped around to '0' - carry = nextIndex === 0; - } else { - // No carry, just append the remaining part of the suffix - result = suffix[i] + result; - } - } +const getIntegrationSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.integration.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - if (carry) { - result = '0' + result; - } +const getEnvironmentSlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.environment.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} - return result; -}; +const getApiKeySlugExists = async ( + slug: Workspace['slug'], + prisma: PrismaService +): Promise => { + const existingSlug = await prisma.apiKey.findMany({ + where: { + slug: { + startsWith: `${slug}-` + } + }, + orderBy: { + slug: 'desc' + }, + take: 1 + }) + return existingSlug.length > 0 ? existingSlug[0].slug : '' +} /** - * Generates a unique slug for the given entity type and name. - * It queries existing slugs, determines the highest suffix, and generates a new unique slug. + * Generates a unique slug for the given entity type and name. It keeps + * generating slugs until it finds one that does not exist in the database. * * @param name The name of the entity. * @param entityType The type of the entity. - * @param prisma The Prisma client to use. + * @param prisma The Prisma client to use to check the existence of the slug. * @returns A unique slug for the given entity. */ export default async function generateEntitySlug( @@ -89,35 +229,33 @@ export default async function generateEntitySlug( | 'API_KEY', prisma: PrismaService ): Promise { - const baseSlug = generateBaseSlug(name) - - const existingSlugs = await prisma[convertEntityTypeToCamelCase(entityType)].findMany({ - where: { - slug: { - startsWith: baseSlug - } - }, - orderBy: { - slug: 'desc' - } - }); - - let highestSuffix = ''; - - if (existingSlugs.length > 0) { - for (const item of existingSlugs) { - const slug = item.slug; - const suffix = slug.substring(baseSlug.length + 1); - - if (suffix > highestSuffix) { - highestSuffix = suffix; - } - } + const baseSlug = generateSlugName(name) + let foundSlug = '' + switch (entityType) { + case 'WORKSPACE_ROLE': + foundSlug = await getWorkspaceRoleIfSlugExists(baseSlug, prisma) + break + case 'WORKSPACE': + foundSlug = await getWorkspaceSlugExists(baseSlug, prisma) + break + case 'PROJECT': + foundSlug = await getProjectSlugExists(baseSlug, prisma) + break + case 'VARIABLE': + foundSlug = await getVariableSlugExists(baseSlug, prisma) + break + case 'SECRET': + foundSlug = await getSecretSlugExists(baseSlug, prisma) + break + case 'INTEGRATION': + foundSlug = await getIntegrationSlugExists(baseSlug, prisma) + break + case 'ENVIRONMENT': + foundSlug = await getEnvironmentSlugExists(baseSlug, prisma) + break + case 'API_KEY': + foundSlug = await getApiKeySlugExists(baseSlug, prisma) + break } - - // Increment the highest suffix to generate the new unique slug - const newSuffix = incrementSlugSuffix(highestSuffix); - const uniqueSlug = `${baseSlug}-${newSuffix}`; - - return uniqueSlug; + return incrementSlugSuffix(foundSlug, baseSlug) } From 5836d466ab092e4824d9dfe43225966adcdead24 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Thu, 12 Sep 2024 13:53:52 +0530 Subject: [PATCH 06/11] Extra linting changes --- apps/api/src/project/project.e2e.spec.ts | 16 ++++++++++------ .../dto/create.workspace/create.workspace.ts | 2 +- .../src/workspace/service/workspace.service.ts | 8 ++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/api/src/project/project.e2e.spec.ts b/apps/api/src/project/project.e2e.spec.ts index 88b81f49..8aa49e30 100644 --- a/apps/api/src/project/project.e2e.spec.ts +++ b/apps/api/src/project/project.e2e.spec.ts @@ -852,12 +852,16 @@ describe('Project Controller Tests', () => { ) // Add user to workspace as a member - await workspaceMembershipService.inviteUsersToWorkspace(user1, workspace1.slug, [ - { - email: johnny.email, - roleSlugs: [role.slug] - } - ]) + await workspaceMembershipService.inviteUsersToWorkspace( + user1, + workspace1.slug, + [ + { + email: johnny.email, + roleSlugs: [role.slug] + } + ] + ) // Accept the invitation on behalf of the user await workspaceMembershipService.acceptInvitation(johnny, workspace1.slug) diff --git a/apps/api/src/workspace/dto/create.workspace/create.workspace.ts b/apps/api/src/workspace/dto/create.workspace/create.workspace.ts index 032206bb..632e360f 100644 --- a/apps/api/src/workspace/dto/create.workspace/create.workspace.ts +++ b/apps/api/src/workspace/dto/create.workspace/create.workspace.ts @@ -9,4 +9,4 @@ export class CreateWorkspace { @IsString() @IsOptional() description?: string -} \ No newline at end of file +} diff --git a/apps/api/src/workspace/service/workspace.service.ts b/apps/api/src/workspace/service/workspace.service.ts index 21bc24d8..4daea549 100644 --- a/apps/api/src/workspace/service/workspace.service.ts +++ b/apps/api/src/workspace/service/workspace.service.ts @@ -4,9 +4,7 @@ import { createEvent } from '@/common/event' import { paginate } from '@/common/paginate' import generateEntitySlug from '@/common/slug-generator' import { limitMaxItemsPerPage } from '@/common/util' -import { - createWorkspace -} from '@/common/workspace' +import { createWorkspace } from '@/common/workspace' import { IMailService, MAIL_SERVICE } from '@/mail/services/interface.service' import { PrismaService } from '@/prisma/prisma.service' import { @@ -29,9 +27,7 @@ import { Variable, Workspace } from '@prisma/client' -import { - CreateWorkspace, -} from '../dto/create.workspace/create.workspace' +import { CreateWorkspace } from '../dto/create.workspace/create.workspace' import { UpdateWorkspace } from '../dto/update.workspace/update.workspace' @Injectable() From 098ef635be06b74bb5e4d30430aa3fb28a442492 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Thu, 12 Sep 2024 15:16:37 +0530 Subject: [PATCH 07/11] Updated slug-genrator.ts file --- apps/api/src/common/slug-generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 45bffe37..ba12497c 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -6,7 +6,7 @@ const incrementSlugSuffix = (foundSlug: string, baseSlug: string): string => { let suffix = '' - if (!foundSlug) { + if (foundSlug) { suffix = foundSlug.substring(baseSlug.length + 1) } @@ -44,11 +44,11 @@ const incrementSlugSuffix = (foundSlug: string, baseSlug: string): string => { } /** - * Generates a unique slug for the given name. It keeps generating slugs until it finds + * Generates a slug for the given name. It keeps generating slugs until it finds * one that does not exist in the database. * * @param name The name of the entity. - * @returns A unique slug for the given entity. + * @returns A alphanumeric slug for the given name. */ const generateSlugName = (name: string): string => { // Convert to lowercase From 94319cbf46ff25a1de58e81decae65dc6cf82e2e Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Sat, 14 Sep 2024 17:02:37 +0530 Subject: [PATCH 08/11] Added tests for slug generation --- apps/api/src/common/slug-generator.spec.ts | 115 +++++++++++++++++++++ apps/api/src/common/slug-generator.ts | 31 +++++- 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/common/slug-generator.spec.ts diff --git a/apps/api/src/common/slug-generator.spec.ts b/apps/api/src/common/slug-generator.spec.ts new file mode 100644 index 00000000..7d69fdc2 --- /dev/null +++ b/apps/api/src/common/slug-generator.spec.ts @@ -0,0 +1,115 @@ +import { PrismaService } from '@/prisma/prisma.service' +import generateEntitySlug, { + generateSlugName, + incrementSlugSuffix +} from './slug-generator' +import { mockDeep } from 'jest-mock-extended' + +describe('generateEntitySlug', () => { + let prisma + + beforeEach(() => { + prisma = mockDeep() + }) + + describe('generateSlugName', () => { + it('should convert name to slug format', () => { + expect(generateSlugName('Hello World')).toBe('hello-world') + expect(generateSlugName('Entity with 123')).toBe('entity-with-123') + expect(generateSlugName('Special #Name!')).toBe('special--name-') + }) + }) + + describe('incrementSlugSuffix', () => { + it('should return base slug with `-0` when no suffix is found', () => { + const result = incrementSlugSuffix('', 'my-slug') + expect(result).toBe('my-slug-0') + }) + + it('should increment suffix when found', () => { + const result = incrementSlugSuffix('my-slug-0', 'my-slug') + expect(result).toBe('my-slug-1') + }) + + it('should handle complex increment cases with carryover', () => { + const result = incrementSlugSuffix('my-slug-z', 'my-slug') + expect(result).toBe('my-slug-00') + }) + }) + + describe('generateEntitySlug for each entity type', () => { + it('should generate a unique slug for WORKSPACE_ROLE', async () => { + prisma.workspaceRole.findMany.mockResolvedValue([ + { + slug: 'workspace-role-0' + } + ]) + + const slug = await generateEntitySlug( + 'Workspace Role', + 'WORKSPACE_ROLE', + prisma + ) + expect(slug).toBe('workspace-role-1') + }) + + it('should generate a unique slug for WORKSPACE', async () => { + prisma.workspace.findMany.mockResolvedValue([]) + + const slug = await generateEntitySlug('Workspace', 'WORKSPACE', prisma) + expect(slug).toBe('workspace-0') + }) + + it('should generate a unique slug for PROJECT', async () => { + prisma.project.findMany.mockResolvedValue([{ slug: 'project-z' }]) + + const slug = await generateEntitySlug('Project', 'PROJECT', prisma) + expect(slug).toBe('project-00') + }) + + it('should generate a unique slug for VARIABLE', async () => { + prisma.variable.findMany.mockResolvedValue([{ slug: 'variable-az' }]) + + const slug = await generateEntitySlug('Variable', 'VARIABLE', prisma) + expect(slug).toBe('variable-b0') + }) + + it('should generate a unique slug for SECRET', async () => { + prisma.secret.findMany.mockResolvedValue([{ slug: 'secret-9' }]) + + const slug = await generateEntitySlug('Secret', 'SECRET', prisma) + expect(slug).toBe('secret-a') + }) + + it('should generate a unique slug for INTEGRATION', async () => { + prisma.integration.findMany.mockResolvedValue([{ slug: 'integration-b' }]) + + const slug = await generateEntitySlug( + 'Integration', + 'INTEGRATION', + prisma + ) + expect(slug).toBe('integration-c') + }) + + it('should generate a unique slug for ENVIRONMENT', async () => { + prisma.environment.findMany.mockResolvedValue([ + { slug: 'environment-zz' } + ]) + + const slug = await generateEntitySlug( + 'Environment', + 'ENVIRONMENT', + prisma + ) + expect(slug).toBe('environment-000') + }) + + it('should generate a unique slug for API_KEY', async () => { + prisma.apiKey.findMany.mockResolvedValue([{ slug: 'api--key-09' }]) + + const slug = await generateEntitySlug('Api @Key', 'API_KEY', prisma) + expect(slug).toBe('api--key-0a') + }) + }) +}) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index ba12497c..61bf3e37 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,7 +1,10 @@ import { PrismaService } from '@/prisma/prisma.service' import { Workspace } from '@prisma/client' -const incrementSlugSuffix = (foundSlug: string, baseSlug: string): string => { +export const incrementSlugSuffix = ( + foundSlug: string, + baseSlug: string +): string => { const charset = '0123456789abcdefghijklmnopqrstuvwxyz' let suffix = '' @@ -50,7 +53,7 @@ const incrementSlugSuffix = (foundSlug: string, baseSlug: string): string => { * @param name The name of the entity. * @returns A alphanumeric slug for the given name. */ -const generateSlugName = (name: string): string => { +export const generateSlugName = (name: string): string => { // Convert to lowercase const lowerCaseName = name.trim().toLowerCase() @@ -73,6 +76,9 @@ const getWorkspaceRoleIfSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -91,6 +97,9 @@ const getWorkspaceSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -109,6 +118,9 @@ const getProjectSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -127,6 +139,9 @@ const getVariableSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -145,6 +160,9 @@ const getSecretSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -163,6 +181,9 @@ const getIntegrationSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -181,6 +202,9 @@ const getEnvironmentSlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, @@ -199,6 +223,9 @@ const getApiKeySlugExists = async ( startsWith: `${slug}-` } }, + select: { + slug: true + }, orderBy: { slug: 'desc' }, From 3a34103964e39ad1009c7dac2312918f4c829434 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Sat, 14 Sep 2024 17:41:36 +0530 Subject: [PATCH 09/11] FIxed some typos --- apps/api/src/common/slug-generator.spec.ts | 6 ++--- apps/api/src/common/slug-generator.ts | 28 +++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/api/src/common/slug-generator.spec.ts b/apps/api/src/common/slug-generator.spec.ts index 7d69fdc2..d4e0178c 100644 --- a/apps/api/src/common/slug-generator.spec.ts +++ b/apps/api/src/common/slug-generator.spec.ts @@ -16,7 +16,7 @@ describe('generateEntitySlug', () => { it('should convert name to slug format', () => { expect(generateSlugName('Hello World')).toBe('hello-world') expect(generateSlugName('Entity with 123')).toBe('entity-with-123') - expect(generateSlugName('Special #Name!')).toBe('special--name-') + expect(generateSlugName('Special #Name!')).toBe('special-name') }) }) @@ -106,10 +106,10 @@ describe('generateEntitySlug', () => { }) it('should generate a unique slug for API_KEY', async () => { - prisma.apiKey.findMany.mockResolvedValue([{ slug: 'api--key-09' }]) + prisma.apiKey.findMany.mockResolvedValue([{ slug: 'api-key-09' }]) const slug = await generateEntitySlug('Api @Key', 'API_KEY', prisma) - expect(slug).toBe('api--key-0a') + expect(slug).toBe('api-key-0a') }) }) }) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 61bf3e37..8ee3b434 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -2,15 +2,15 @@ import { PrismaService } from '@/prisma/prisma.service' import { Workspace } from '@prisma/client' export const incrementSlugSuffix = ( - foundSlug: string, + existingSlug: string, baseSlug: string ): string => { const charset = '0123456789abcdefghijklmnopqrstuvwxyz' let suffix = '' - if (foundSlug) { - suffix = foundSlug.substring(baseSlug.length + 1) + if (existingSlug) { + suffix = existingSlug.substring(baseSlug.length + 1) } if (!suffix) { @@ -61,7 +61,7 @@ export const generateSlugName = (name: string): string => { const hyphenatedName = lowerCaseName.replace(/\s+/g, '-') // Replace all non-alphanumeric characters with hyphens - const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '-') + const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '') return alphanumericName } @@ -257,32 +257,32 @@ export default async function generateEntitySlug( prisma: PrismaService ): Promise { const baseSlug = generateSlugName(name) - let foundSlug = '' + let existingSlug = '' switch (entityType) { case 'WORKSPACE_ROLE': - foundSlug = await getWorkspaceRoleIfSlugExists(baseSlug, prisma) + existingSlug = await getWorkspaceRoleIfSlugExists(baseSlug, prisma) break case 'WORKSPACE': - foundSlug = await getWorkspaceSlugExists(baseSlug, prisma) + existingSlug = await getWorkspaceSlugExists(baseSlug, prisma) break case 'PROJECT': - foundSlug = await getProjectSlugExists(baseSlug, prisma) + existingSlug = await getProjectSlugExists(baseSlug, prisma) break case 'VARIABLE': - foundSlug = await getVariableSlugExists(baseSlug, prisma) + existingSlug = await getVariableSlugExists(baseSlug, prisma) break case 'SECRET': - foundSlug = await getSecretSlugExists(baseSlug, prisma) + existingSlug = await getSecretSlugExists(baseSlug, prisma) break case 'INTEGRATION': - foundSlug = await getIntegrationSlugExists(baseSlug, prisma) + existingSlug = await getIntegrationSlugExists(baseSlug, prisma) break case 'ENVIRONMENT': - foundSlug = await getEnvironmentSlugExists(baseSlug, prisma) + existingSlug = await getEnvironmentSlugExists(baseSlug, prisma) break case 'API_KEY': - foundSlug = await getApiKeySlugExists(baseSlug, prisma) + existingSlug = await getApiKeySlugExists(baseSlug, prisma) break } - return incrementSlugSuffix(foundSlug, baseSlug) + return incrementSlugSuffix(existingSlug, baseSlug) } From 9abe7e3cc16c04f71b03b628b0167b25ecbab432 Mon Sep 17 00:00:00 2001 From: rajdip-b Date: Sat, 14 Sep 2024 19:49:24 +0530 Subject: [PATCH 10/11] updated search --- apps/api/src/common/slug-generator.ts | 185 ++++++++++---------------- 1 file changed, 68 insertions(+), 117 deletions(-) diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 8ee3b434..28d801f3 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,7 +1,7 @@ import { PrismaService } from '@/prisma/prisma.service' import { Workspace } from '@prisma/client' -export const incrementSlugSuffix = ( +const incrementSlugSuffix = ( existingSlug: string, baseSlug: string ): string => { @@ -70,20 +70,14 @@ const getWorkspaceRoleIfSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.workspaceRole.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "WorkspaceRole" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -91,20 +85,14 @@ const getWorkspaceSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.workspace.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Workspace" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -112,20 +100,14 @@ const getProjectSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.project.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Project" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -133,20 +115,14 @@ const getVariableSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.variable.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Variable" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -154,20 +130,14 @@ const getSecretSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.secret.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Secret" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -175,20 +145,14 @@ const getIntegrationSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.integration.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Integration" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -196,20 +160,14 @@ const getEnvironmentSlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.environment.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "Environment" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } @@ -217,31 +175,24 @@ const getApiKeySlugExists = async ( slug: Workspace['slug'], prisma: PrismaService ): Promise => { - const existingSlug = await prisma.apiKey.findMany({ - where: { - slug: { - startsWith: `${slug}-` - } - }, - select: { - slug: true - }, - orderBy: { - slug: 'desc' - }, - take: 1 - }) + const search = `${slug}-[a-z0-9]*` + const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + SELECT slug + FROM "ApiKey" + WHERE slug ~ ${search} + ORDER BY slug DESC + LIMIT 1 + ` return existingSlug.length > 0 ? existingSlug[0].slug : '' } /** - * Generates a unique slug for the given entity type and name. It keeps - * generating slugs until it finds one that does not exist in the database. + * Generates a slug for the given name and entity type. It keeps generating slugs until it finds + * one that does not exist in the database. * * @param name The name of the entity. * @param entityType The type of the entity. - * @param prisma The Prisma client to use to check the existence of the slug. - * @returns A unique slug for the given entity. + * @returns A alphanumeric slug for the given name. */ export default async function generateEntitySlug( name: string, From 7e23c2926313b01eadc699ad83c1e970969aa2b4 Mon Sep 17 00:00:00 2001 From: NIL2000 Date: Sat, 14 Sep 2024 20:40:10 +0530 Subject: [PATCH 11/11] Updated test --- apps/api/src/common/slug-generator.spec.ts | 18 ++++++++---------- apps/api/src/common/slug-generator.ts | 6 ++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/api/src/common/slug-generator.spec.ts b/apps/api/src/common/slug-generator.spec.ts index d4e0178c..a2921839 100644 --- a/apps/api/src/common/slug-generator.spec.ts +++ b/apps/api/src/common/slug-generator.spec.ts @@ -39,7 +39,7 @@ describe('generateEntitySlug', () => { describe('generateEntitySlug for each entity type', () => { it('should generate a unique slug for WORKSPACE_ROLE', async () => { - prisma.workspaceRole.findMany.mockResolvedValue([ + prisma.$queryRaw.mockResolvedValue([ { slug: 'workspace-role-0' } @@ -54,35 +54,35 @@ describe('generateEntitySlug', () => { }) it('should generate a unique slug for WORKSPACE', async () => { - prisma.workspace.findMany.mockResolvedValue([]) + prisma.$queryRaw.mockResolvedValue([]) const slug = await generateEntitySlug('Workspace', 'WORKSPACE', prisma) expect(slug).toBe('workspace-0') }) it('should generate a unique slug for PROJECT', async () => { - prisma.project.findMany.mockResolvedValue([{ slug: 'project-z' }]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'project-z' }]) const slug = await generateEntitySlug('Project', 'PROJECT', prisma) expect(slug).toBe('project-00') }) it('should generate a unique slug for VARIABLE', async () => { - prisma.variable.findMany.mockResolvedValue([{ slug: 'variable-az' }]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'variable-az' }]) const slug = await generateEntitySlug('Variable', 'VARIABLE', prisma) expect(slug).toBe('variable-b0') }) it('should generate a unique slug for SECRET', async () => { - prisma.secret.findMany.mockResolvedValue([{ slug: 'secret-9' }]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'secret-9' }]) const slug = await generateEntitySlug('Secret', 'SECRET', prisma) expect(slug).toBe('secret-a') }) it('should generate a unique slug for INTEGRATION', async () => { - prisma.integration.findMany.mockResolvedValue([{ slug: 'integration-b' }]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'integration-b' }]) const slug = await generateEntitySlug( 'Integration', @@ -93,9 +93,7 @@ describe('generateEntitySlug', () => { }) it('should generate a unique slug for ENVIRONMENT', async () => { - prisma.environment.findMany.mockResolvedValue([ - { slug: 'environment-zz' } - ]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'environment-zz' }]) const slug = await generateEntitySlug( 'Environment', @@ -106,7 +104,7 @@ describe('generateEntitySlug', () => { }) it('should generate a unique slug for API_KEY', async () => { - prisma.apiKey.findMany.mockResolvedValue([{ slug: 'api-key-09' }]) + prisma.$queryRaw.mockResolvedValue([{ slug: 'api-key-09' }]) const slug = await generateEntitySlug('Api @Key', 'API_KEY', prisma) expect(slug).toBe('api-key-0a') diff --git a/apps/api/src/common/slug-generator.ts b/apps/api/src/common/slug-generator.ts index 28d801f3..eca3f47d 100644 --- a/apps/api/src/common/slug-generator.ts +++ b/apps/api/src/common/slug-generator.ts @@ -1,7 +1,7 @@ import { PrismaService } from '@/prisma/prisma.service' import { Workspace } from '@prisma/client' -const incrementSlugSuffix = ( +export const incrementSlugSuffix = ( existingSlug: string, baseSlug: string ): string => { @@ -71,7 +71,9 @@ const getWorkspaceRoleIfSlugExists = async ( prisma: PrismaService ): Promise => { const search = `${slug}-[a-z0-9]*` - const existingSlug = await prisma.$queryRaw<{ slug: string }[]>` + const existingSlug: { slug: string }[] = await prisma.$queryRaw< + { slug: string }[] + >` SELECT slug FROM "WorkspaceRole" WHERE slug ~ ${search}