diff --git a/src/supply-categories/supply-categories.controller.ts b/src/supply-categories/supply-categories.controller.ts index 96106050..7c821f18 100644 --- a/src/supply-categories/supply-categories.controller.ts +++ b/src/supply-categories/supply-categories.controller.ts @@ -43,12 +43,19 @@ export class SupplyCategoriesController { @UseGuards(AdminGuard) async store(@Body() body) { try { + const isDuplicate = await this.supplyCategoryServices.isDuplicate(body); + + if (isDuplicate) { + return new ServerResponse(400, 'This category already exists') + } + const data = await this.supplyCategoryServices.store(body); return new ServerResponse( 200, 'Successfully created supply category', data, ); + } catch (err: any) { this.logger.error(`Failed to create supply category: ${err}`); throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); diff --git a/src/supply-categories/supply-categories.service.spec.ts b/src/supply-categories/supply-categories.service.spec.ts index 80f980c8..ba48f651 100644 --- a/src/supply-categories/supply-categories.service.spec.ts +++ b/src/supply-categories/supply-categories.service.spec.ts @@ -4,22 +4,34 @@ import { SupplyCategoriesService } from './supply-categories.service'; describe('SupplyCategoriesService', () => { let service: SupplyCategoriesService; + let prisma: PrismaService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [SupplyCategoriesService], - }) - .useMocker((token) => { - if (token === PrismaService) { - return {}; - } - }) - .compile(); + providers: [SupplyCategoriesService, PrismaService], + }).compile(); service = module.get(SupplyCategoriesService); + prisma = module.get(PrismaService); + prisma.supplyCategory.findFirst = jest.fn(); // Adicione esta linha }); it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should check for duplicates', async () => { + const body = { + name: 'Test', + }; + + const mockSupply = { + name: 'Test' + } as any; + + jest.spyOn(prisma.supplyCategory, 'findFirst').mockResolvedValue(mockSupply); + + const result = await service.isDuplicate(body); + expect(result).toBe(true); + }); }); diff --git a/src/supply-categories/supply-categories.service.ts b/src/supply-categories/supply-categories.service.ts index 4ebd796a..abcebf06 100644 --- a/src/supply-categories/supply-categories.service.ts +++ b/src/supply-categories/supply-categories.service.ts @@ -7,6 +7,8 @@ import { UpdateSupplyCategorySchema, } from './types'; +import { slugify } from '@/utils/utils'; + @Injectable() export class SupplyCategoriesService { constructor(private readonly prismaService: PrismaService) {} @@ -37,4 +39,26 @@ export class SupplyCategoriesService { async index() { return await this.prismaService.supplyCategory.findMany({}); } + + async isDuplicate(body: z.infer) : Promise { + + const payload = CreateSupplyCategorySchema.parse(body); + const existingData = await this.prismaService.supplyCategory.findFirst({ + where: { + name: payload.name + }, + select: { + name: true, + }, + }); + + const payloadName = slugify(payload.name) + const existingDataName = slugify(existingData?.name) + + if (payloadName === existingDataName) { + return true + } + + return false + } } diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 55133484..87166fef 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -33,9 +33,17 @@ export class SupplyController { @Post('') async store(@Body() body) { - try { + try { + + const isDuplicate = await this.supplyServices.isDuplicate(body); + + if (isDuplicate) { + return new ServerResponse(400, 'This supply already exists') + } + const data = await this.supplyServices.store(body); return new ServerResponse(200, 'Successfully created supply', data); + } catch (err: any) { this.logger.error(`Failed to create supply: ${err}`); throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); diff --git a/src/supply/supply.service.spec.ts b/src/supply/supply.service.spec.ts index 7868d160..d59380e5 100644 --- a/src/supply/supply.service.spec.ts +++ b/src/supply/supply.service.spec.ts @@ -4,22 +4,38 @@ import { PrismaService } from 'src/prisma/prisma.service'; describe('SupplyService', () => { let service: SupplyService; + let prisma: PrismaService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [SupplyService], - }) - .useMocker((token) => { - if (token === PrismaService) { - return {}; - } - }) - .compile(); + providers: [SupplyService, PrismaService], + }).compile(); service = module.get(SupplyService); + prisma = module.get(PrismaService); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should check for duplicates', async () => { + const body = { + name: 'Test', + supplyCategoryId: '1', + }; + + const mockSupply = { + name: 'Test', + supplyCategory: { + id: '1', + }, + } as any; + + jest.spyOn(prisma.supply, 'findFirst').mockResolvedValue(mockSupply); + + const result = await service.isDuplicate(body); + expect(result).toBe(true); + }); + }); diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index a3caf57e..a65e6234 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -4,12 +4,15 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateSupplySchema, UpdateSupplySchema } from './types'; +import { slugify } from '@/utils/utils'; + @Injectable() export class SupplyService { constructor(private readonly prismaService: PrismaService) {} async store(body: z.infer) { const payload = CreateSupplySchema.parse(body); + return await this.prismaService.supply.create({ data: { ...payload, @@ -53,4 +56,33 @@ export class SupplyService { return data; } + + async isDuplicate(body: z.infer):Promise { + + const payload = CreateSupplySchema.parse(body); + const payloadName = slugify(payload.name) + + const existingData = await this.prismaService.supply.findFirst({ + where: { + name: payload.name + }, + select: { + name: true, + supplyCategory: { + select: { + id: true, + }, + }, + }, + }); + const existingDataName = slugify(existingData?.name) + + if (existingDataName === payloadName + && payload.supplyCategoryId === existingData?.supplyCategory.id) { + return true + } + + return false; + } + } diff --git a/src/utils/utils.spec.ts b/src/utils/utils.spec.ts new file mode 100644 index 00000000..1a1169ba --- /dev/null +++ b/src/utils/utils.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { slugify } from './utils'; + +describe('slugify function', () => { + + it('should handle accented characters', () => { + const result = slugify('tést'); + expect(result).toBe('test'); + }); + + it('should handle uppercase characters', () => { + const result = slugify('Test'); + expect(result).toBe('test'); + }); + + it('should handle double spaces', () => { + const result = slugify('test test'); + expect(result).toBe('test-test'); + }); +}); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 378e9914..70a91791 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -117,6 +117,13 @@ function removeEmptyStrings(obj): T { ) as T; } +function slugify(str:string | void):string { + + const slugified = String(str).normalize('NFKD').replace(/[\u0300-\u036f]/g, '').trim().toLowerCase() + .replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-') + return slugified + +} export { ServerResponse, calculateGeolocationBounds, @@ -125,4 +132,5 @@ export { getSessionData, removeNotNumbers, removeEmptyStrings, + slugify };