diff --git a/package.json b/package.json index e0c0842..f32f918 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:coverage": "vitest run --coverage", "test:ui": "vitest --ui", "db:generate": "prisma generate", - "db:migrate:dev": "prisma migrate dev", + "db:migrate": "prisma migrate dev", "db:studio": "prisma studio" }, "keywords": [], @@ -30,6 +30,7 @@ "zod": "^3.25.67" }, "devDependencies": { + "@faker-js/faker": "^9.9.0", "@types/node": "^24.0.4", "@vitest/coverage-v8": "3.2.4", "@vitest/ui": "3.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d75d35..291651e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: specifier: ^3.25.67 version: 3.25.67 devDependencies: + '@faker-js/faker': + specifier: ^9.9.0 + version: 9.9.0 '@types/node': specifier: ^24.0.4 version: 24.0.4 @@ -235,6 +238,10 @@ packages: cpu: [x64] os: [win32] + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fastify/ajv-compiler@4.0.2': resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} @@ -1687,6 +1694,8 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true + '@faker-js/faker@9.9.0': {} + '@fastify/ajv-compiler@4.0.2': dependencies: ajv: 8.17.1 diff --git a/prisma/migrations/20250711015614_create_pets/migration.sql b/prisma/migrations/20250711015614_create_pets/migration.sql new file mode 100644 index 0000000..87f6580 --- /dev/null +++ b/prisma/migrations/20250711015614_create_pets/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "pets" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "about" TEXT NOT NULL, + "age" TEXT NOT NULL, + "size" TEXT NOT NULL, + "energy_level" TEXT NOT NULL, + "environment" TEXT NOT NULL, + "org_id" TEXT NOT NULL, + + CONSTRAINT "pets_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "pets" ADD CONSTRAINT "pets_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "orgs"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 86bb614..657bbc7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,5 +25,22 @@ model Orgs { latitude Decimal longitude Decimal + pets Pets[] + @@map("orgs") } + +model Pets { + id String @id @default(uuid()) + name String + about String + age String + size String + energy_level String + environment String + org_id String + + org Orgs @relation(fields: [org_id], references: [id], onDelete: Restrict) + + @@map("pets") +} diff --git a/src/http/controllers/pets/createPetsController.ts b/src/http/controllers/pets/createPetsController.ts new file mode 100644 index 0000000..113fd4b --- /dev/null +++ b/src/http/controllers/pets/createPetsController.ts @@ -0,0 +1,38 @@ +import { OrgNotFoundError } from "@/use-cases/errors/orgNotFoundError"; +import { makeCreatePetsUseCase } from "@/use-cases/factories/makeCreatePetsUseCase"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const createPetsController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const createOrgsBodySchema = z.object({ + idOrg: z.string(), + name: z.string(), + about: z.string(), + age: z.string(), + size: z.string(), + energy_level: z.string(), + environment: z.string(), + }); + + const bodyData = createOrgsBodySchema.parse(request.body); + + try + { + const createOrgsUseCase = makeCreatePetsUseCase(); + await createOrgsUseCase.execute(bodyData); + + } catch (error) + { + if (error instanceof OrgNotFoundError) + { + return reply.status(404).send(error.message); + } + + throw error; + } + + reply.status(201).send(); +} diff --git a/src/http/controllers/pets/getPetsController.ts b/src/http/controllers/pets/getPetsController.ts new file mode 100644 index 0000000..4127b1c --- /dev/null +++ b/src/http/controllers/pets/getPetsController.ts @@ -0,0 +1,31 @@ +import { ResourceNotFoundError } from '@/use-cases/errors/resourceNotFound'; +import { makeGetPetsUseCase } from '@/use-cases/factories/makeGetPetsUseCase'; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const getPetsController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const getPetsQuerySchema = z.object({ + id: z.string() + }); + + const paramsData = getPetsQuerySchema.parse(request.params); + + try + { + const getPetsUseCase = makeGetPetsUseCase(); + const { pet } = await getPetsUseCase.execute(paramsData); + + reply.status(200).send(pet); + } catch (error) + { + if (error instanceof ResourceNotFoundError) + { + return reply.status(404).send(error.message); + } + + throw error; + } +} diff --git a/src/http/controllers/pets/listPetsController.ts b/src/http/controllers/pets/listPetsController.ts new file mode 100644 index 0000000..7b0bf06 --- /dev/null +++ b/src/http/controllers/pets/listPetsController.ts @@ -0,0 +1,29 @@ +import { makeListPetsUseCase } from "@/use-cases/factories/makeListPetsUseCase"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const listPetsController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const getPetsQuerySchema = z.object({ + city: z.string(), + age: z.string().optional(), + size: z.string().optional(), + energy_level: z.string().optional(), + environment: z.string().optional() + }); + + const queryData = getPetsQuerySchema.parse(request.query); + + try + { + const listPetsUseCase = makeListPetsUseCase(); + const { pets } = await listPetsUseCase.execute(queryData); + + reply.status(200).send(pets); + } catch (error) + { + throw error; + } +} diff --git a/src/http/controllers/pets/petsRoutes.ts b/src/http/controllers/pets/petsRoutes.ts new file mode 100644 index 0000000..fa1aae7 --- /dev/null +++ b/src/http/controllers/pets/petsRoutes.ts @@ -0,0 +1,11 @@ +import { FastifyInstance } from "fastify"; +import { createPetsController } from "./createPetsController"; +import { getPetsController } from "./getPetsController"; +import { listPetsController } from "./listPetsController"; + + +export const petsRoutes = async (app: FastifyInstance) => { + app.post('/pets', createPetsController); + app.get('/pets/:id', getPetsController); + app.get('/pets', listPetsController); +} \ No newline at end of file diff --git a/src/http/routes.ts b/src/http/routes.ts index 58ebba8..a79f9ab 100644 --- a/src/http/routes.ts +++ b/src/http/routes.ts @@ -1,8 +1,10 @@ import { FastifyInstance } from "fastify"; import { statusRoutes } from "./controllers/status/statusRouts"; import { orgsRoutes } from "./controllers/orgs/orgsRoutes"; +import { petsRoutes } from "./controllers/pets/petsRoutes"; export const appRoutes = async (app: FastifyInstance) => { app.register(statusRoutes); - app.register(orgsRoutes) + app.register(orgsRoutes); + app.register(petsRoutes); } \ No newline at end of file diff --git a/src/http/server.ts b/src/http/server.ts index 654525b..28da7b8 100644 --- a/src/http/server.ts +++ b/src/http/server.ts @@ -14,7 +14,7 @@ app.setErrorHandler((error, _, reply) => { { return reply.status(400).send({ message: 'Validation error', - issues: error.format(), + issues: error.issues, }); } diff --git a/src/repositories/IPetsRepository.ts b/src/repositories/IPetsRepository.ts new file mode 100644 index 0000000..1f29326 --- /dev/null +++ b/src/repositories/IPetsRepository.ts @@ -0,0 +1,15 @@ +import { Prisma, Pets } from "prisma/generated/prisma" + +export interface FindAllParams { + city: string + age?: string + size?: string + energy_level?: string + environment?: string +} + +export interface IPetsRepository { + create(data: Prisma.PetsCreateManyInput): Promise + findById(id: string): Promise + findAll(data: FindAllParams): Promise +} \ No newline at end of file diff --git a/src/repositories/in-memory/petsRepositoryInMemory.ts b/src/repositories/in-memory/petsRepositoryInMemory.ts new file mode 100644 index 0000000..794de75 --- /dev/null +++ b/src/repositories/in-memory/petsRepositoryInMemory.ts @@ -0,0 +1,52 @@ + + +import { randomUUID } from 'node:crypto' +import { FindAllParams, IPetsRepository } from '@/repositories/IPetsRepository' +import { Pets, Prisma } from 'prisma/generated/prisma' +import { OrgsRepositoryInMemory } from './orgsRepositoryInMemory' + +export class PetsRepositoryInMemory implements IPetsRepository { + public items: Pets[] = [] + constructor(private orgsRepository: OrgsRepositoryInMemory) { } + + async create({ id, ...data }: Prisma.PetsCreateManyInput) { + const pet: Pets = { + ...data, + id: id ?? randomUUID(), + } + + this.items.push(pet) + + return pet; + } + + async findById(id: string) { + const pet = this.items.find((item) => item.id === id) + + if (!pet) + { + return null + } + + return pet + } + + async findAll(params: FindAllParams): Promise { + const orgsByCity = this.orgsRepository.items.filter( + (org) => org.city === params.city, + ) + + const pets = this.items + .filter((item) => orgsByCity.some((org) => org.id === item.org_id)) + .filter((item) => (params.age ? item.age === params.age : true)) + .filter((item) => (params.size ? item.size === params.size : true)) + .filter((item) => + params.energy_level ? item.energy_level === params.energy_level : true, + ) + .filter((item) => + params.environment ? item.environment === params.environment : true, + ) + + return pets + } +} \ No newline at end of file diff --git a/src/repositories/prisma/petsRepositoryPrisma.ts b/src/repositories/prisma/petsRepositoryPrisma.ts new file mode 100644 index 0000000..921b5ee --- /dev/null +++ b/src/repositories/prisma/petsRepositoryPrisma.ts @@ -0,0 +1,41 @@ +import { prisma } from '@/libs/prisma'; +import { Prisma, Pets } from "prisma/generated/prisma" +import { FindAllParams, IPetsRepository } from '../IPetsRepository'; + +export class PetsRepositoryPrisma implements IPetsRepository { + async create(data: Prisma.PetsCreateManyInput): Promise { + const ret = await prisma.pets.create({ data }); + + return ret; + } + + async findById(id: string): Promise { + const ret = await prisma.pets.findUnique({ + where: { + id + } + }); + + return ret; + } + + async findAll(data: FindAllParams): Promise { + const ret = await prisma.pets.findMany({ + where: { + age: data.age, + size: data.size, + energy_level: data.energy_level, + environment: data.environment, + org: { + city: { + contains: data.city, + mode: 'insensitive' + } + } + } + }) + + return ret; + } + +} \ No newline at end of file diff --git a/src/use-cases/errors/orgNotFoundError.ts b/src/use-cases/errors/orgNotFoundError.ts new file mode 100644 index 0000000..a548648 --- /dev/null +++ b/src/use-cases/errors/orgNotFoundError.ts @@ -0,0 +1,5 @@ +export class OrgNotFoundError extends Error { + constructor() { + super('Organization not found.') + } +} \ No newline at end of file diff --git a/src/use-cases/factories/makeCreatePetsUseCase.ts b/src/use-cases/factories/makeCreatePetsUseCase.ts new file mode 100644 index 0000000..0631dd3 --- /dev/null +++ b/src/use-cases/factories/makeCreatePetsUseCase.ts @@ -0,0 +1,11 @@ +import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; +import { PetsRepositoryPrisma } from "@/repositories/prisma/petsRepositoryPrisma"; +import { CreatePetsUseCase } from "../pets/createPetsUseCase"; + +export function makeCreatePetsUseCase() { + const orgsRepository = new OrgsRepositoryPrisma() + const petsRepository = new PetsRepositoryPrisma() + const createPetsUseCase = new CreatePetsUseCase(petsRepository, orgsRepository) + + return createPetsUseCase; +} \ No newline at end of file diff --git a/src/use-cases/factories/makeGetPetsUseCase.ts b/src/use-cases/factories/makeGetPetsUseCase.ts new file mode 100644 index 0000000..f02c0ac --- /dev/null +++ b/src/use-cases/factories/makeGetPetsUseCase.ts @@ -0,0 +1,9 @@ +import { PetsRepositoryPrisma } from "@/repositories/prisma/petsRepositoryPrisma"; +import { GetPetUseCase } from "../pets/getPetUseCase"; + +export function makeGetPetsUseCase() { + const petsRepository = new PetsRepositoryPrisma() + const getPetUseCase = new GetPetUseCase(petsRepository) + + return getPetUseCase; +} \ No newline at end of file diff --git a/src/use-cases/factories/makeListPetsUseCase.ts b/src/use-cases/factories/makeListPetsUseCase.ts new file mode 100644 index 0000000..c855df2 --- /dev/null +++ b/src/use-cases/factories/makeListPetsUseCase.ts @@ -0,0 +1,10 @@ +import { PetsRepositoryPrisma } from "@/repositories/prisma/petsRepositoryPrisma"; +import { GetPetUseCase } from "../pets/getPetUseCase"; +import { ListPetUseCase } from "../pets/listPetUseCase"; + +export function makeListPetsUseCase() { + const petsRepository = new PetsRepositoryPrisma() + const listPetsUseCase = new ListPetUseCase(petsRepository) + + return listPetsUseCase; +} \ No newline at end of file diff --git a/src/use-cases/factories/makeOrg.factory.ts b/src/use-cases/factories/makeOrg.factory.ts new file mode 100644 index 0000000..66508d4 --- /dev/null +++ b/src/use-cases/factories/makeOrg.factory.ts @@ -0,0 +1,25 @@ +import { faker } from '@faker-js/faker' +import crypto from 'node:crypto' + +type Overwrite = { + password?: string + city?: string +} + +export function makeOrg(overwrite?: Overwrite) { + return { + id: crypto.randomUUID(), + author_name: faker.person.fullName(), + cep: faker.location.zipCode(), + city: faker.location.city(), + email: faker.internet.email(), + latitude: faker.location.latitude(), + longitude: faker.location.longitude(), + name: faker.company.name(), + neighborhood: faker.location.streetAddress(), + password: overwrite?.password ?? faker.internet.password(), + state: faker.location.state(), + street: faker.location.street(), + whatsapp: faker.phone.number(), + } +} \ No newline at end of file diff --git a/src/use-cases/factories/makePet.factory.ts b/src/use-cases/factories/makePet.factory.ts new file mode 100644 index 0000000..1a5c0bf --- /dev/null +++ b/src/use-cases/factories/makePet.factory.ts @@ -0,0 +1,27 @@ +import { faker } from '@faker-js/faker' +import crypto from 'node:crypto' + +type Overwrite = { + org_id?: string + age?: string + size?: string + energy_level?: string + environment?: string +} + +export function makePet(overwrite?: Overwrite) { + return { + id: crypto.randomUUID(), + org_id: overwrite?.org_id ?? crypto.randomUUID(), + name: faker.animal.dog(), + about: faker.lorem.paragraph(), + age: overwrite?.age ?? faker.number.int().toString(), + size: + overwrite?.size ?? + faker.helpers.arrayElement(['small', 'medium', 'large']), + energy_level: + overwrite?.energy_level ?? + faker.helpers.arrayElement(['low', 'medium', 'high']), + environment: faker.helpers.arrayElement(['indoor', 'outdoor']), + } +} \ No newline at end of file diff --git a/src/use-cases/pets/createPetsUseCase.spec.ts b/src/use-cases/pets/createPetsUseCase.spec.ts new file mode 100644 index 0000000..e069ae9 --- /dev/null +++ b/src/use-cases/pets/createPetsUseCase.spec.ts @@ -0,0 +1,72 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { CreatePetsUseCase } from './createPetsUseCase'; +import { PetsRepositoryInMemory } from '@/repositories/in-memory/petsRepositoryInMemory'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { OrgNotFoundError } from '../errors/orgNotFoundError'; + +describe("Caso de Uso: Criação de Pets", () => { + let orgsRepository: OrgsRepositoryInMemory; + let petsRepository: PetsRepositoryInMemory; + let sut: CreatePetsUseCase; + + let idOrg: string; + + beforeEach(async () => { + petsRepository = new PetsRepositoryInMemory(); + orgsRepository = new OrgsRepositoryInMemory(); + + sut = new CreatePetsUseCase(petsRepository, orgsRepository); + + const org = await orgsRepository.create({ + name: "Org Test", + author_name: "John Doe", + email: "john@org.com", + whatsapp: "123456789", + password: "123456", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }); + + idOrg = org.id; + }) + + it("Deve ser possivel cadastrar um Pet", async () => { + + const input = { + name: 'Alfredo', + about: 'Cachoro', + age: '12', + size: 'Grande', + energy_level: '2', + environment: 'asdf', + idOrg: idOrg, + }; + + const { pet } = await sut.execute(input); + + expect(pet).toHaveProperty('id') + expect(pet.id).toEqual(expect.any(String)) + + }); + + it("Não deve ser possivel cadastrar um Pet para uma organização inexistente", async () => { + + const input = { + name: 'Alfredo', + about: 'Cachoro', + age: '12', + size: 'Grande', + energy_level: '2', + environment: 'asdf', + idOrg: 'non-existing-id', + }; + + await expect(sut.execute(input)).rejects.instanceOf(OrgNotFoundError); + }); + +}); diff --git a/src/use-cases/pets/createPetsUseCase.ts b/src/use-cases/pets/createPetsUseCase.ts new file mode 100644 index 0000000..a6edbcd --- /dev/null +++ b/src/use-cases/pets/createPetsUseCase.ts @@ -0,0 +1,45 @@ +import { IOrgsRepository } from "@/repositories/IOrgsRepository"; +import { IPetsRepository } from "@/repositories/IPetsRepository"; +import { Pets } from "prisma/generated/prisma"; +import { OrgNotFoundError } from "../errors/orgNotFoundError"; + +type CreatePetsCaseRequest = { + idOrg: string; + name: string; + about: string; + age: string; + size: string; + energy_level: string; + environment: string; +} + +interface CreatePetsCaseResponse { + pet: Pets +} + +export class CreatePetsUseCase { + + constructor( + private petsRepository: IPetsRepository, + private orgsRepository: IOrgsRepository + + ) { } + + async execute({ + idOrg, + ...data + }: CreatePetsCaseRequest): Promise { + const org = await this.orgsRepository.findById(idOrg) + if (!org) + { + throw new OrgNotFoundError() + } + + const pet = await this.petsRepository.create({ + ...data, + org_id: idOrg, + }) + + return { pet }; + } +} \ No newline at end of file diff --git a/src/use-cases/pets/getPetUseCase.ts b/src/use-cases/pets/getPetUseCase.ts new file mode 100644 index 0000000..9ce0d89 --- /dev/null +++ b/src/use-cases/pets/getPetUseCase.ts @@ -0,0 +1,31 @@ +import { IPetsRepository } from "@/repositories/IPetsRepository"; +import { Pets } from "prisma/generated/prisma"; +import { ResourceNotFoundError } from "../errors/resourceNotFound"; + +interface GetPetUseCaseRequest { + id: string; +} + +interface GetPetUseCaseResponse { + pet: Pets +} + +export class GetPetUseCase { + + constructor( + private petsRepository: IPetsRepository + ) { } + + async execute({ id }: GetPetUseCaseRequest): Promise { + const pet = await this.petsRepository.findById(id) + + if (!pet) + { + throw new ResourceNotFoundError(); + } + + return { + pet + } + } +} \ No newline at end of file diff --git a/src/use-cases/pets/getPetsUseCase.spec.ts b/src/use-cases/pets/getPetsUseCase.spec.ts new file mode 100644 index 0000000..a0fa609 --- /dev/null +++ b/src/use-cases/pets/getPetsUseCase.spec.ts @@ -0,0 +1,37 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { PetsRepositoryInMemory } from '@/repositories/in-memory/petsRepositoryInMemory'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { GetPetUseCase } from './getPetUseCase'; +import { ResourceNotFoundError } from '../errors/resourceNotFound'; +import { makeOrg } from '../factories/makeOrg.factory'; +import { makePet } from '../factories/makePet.factory'; + +describe("Caso de Uso: Get Pet", () => { + let orgsRepository: OrgsRepositoryInMemory; + let petsRepository: PetsRepositoryInMemory; + let sut: GetPetUseCase; + + beforeEach(async () => { + orgsRepository = new OrgsRepositoryInMemory(); + petsRepository = new PetsRepositoryInMemory(orgsRepository); + sut = new GetPetUseCase(petsRepository); + }) + + it("Deve ser possivel recuperar os dados do Pet", async () => { + const org = await orgsRepository.create(makeOrg()) + const pet = await petsRepository.create(makePet({ org_id: org.id })) + + const result = await sut.execute({ id: pet.id }) + + expect(result.pet.id).toEqual(pet.id) + expect(result.pet.name).toEqual(pet.name) + }); + + it("Não deve ser possivel recuperar os dados do Pet", async () => { + + await expect(sut.execute({ + id: 'non-existing-id' + })).rejects.instanceOf(ResourceNotFoundError); + }); + +}); diff --git a/src/use-cases/pets/listPetUseCase.spec.ts b/src/use-cases/pets/listPetUseCase.spec.ts new file mode 100644 index 0000000..1a171d9 --- /dev/null +++ b/src/use-cases/pets/listPetUseCase.spec.ts @@ -0,0 +1,102 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { PetsRepositoryInMemory } from '@/repositories/in-memory/petsRepositoryInMemory'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { ListPetUseCase } from './listPetUseCase'; +import { makePet } from '../factories/makePet.factory'; +import { makeOrg } from '../factories/makeOrg.factory'; + +describe("Caso de Uso: Get Pet", () => { + let orgsRepository: OrgsRepositoryInMemory; + let petsRepository: PetsRepositoryInMemory; + let sut: ListPetUseCase; + + beforeEach(async () => { + orgsRepository = new OrgsRepositoryInMemory(); + petsRepository = new PetsRepositoryInMemory(orgsRepository); + + sut = new ListPetUseCase(petsRepository); + }) + + it("Deve ser possivel listar os pets de uma cidade", async () => { + const org = await orgsRepository.create(makeOrg({ city: 'city1' })); + await petsRepository.create(makePet({ org_id: org.id })) + await petsRepository.create(makePet({ org_id: org.id })) + + const org2 = await orgsRepository.create(makeOrg({ city: 'city2' })); + await petsRepository.create(makePet({ org_id: org2.id })) + + const { pets } = await sut.execute({ city: org.city }) + + expect(pets).toHaveLength(2) + + const { pets: pets2 } = await sut.execute({ city: org2.city }) + expect(pets2).toHaveLength(1) + }); + + it("Deve ser possivel listar os pets de uma cidade filtrando por 'age'", async () => { + const org = await orgsRepository.create(makeOrg({ city: 'city1' })); + await petsRepository.create(makePet({ org_id: org.id, age: '1' })) + await petsRepository.create(makePet({ org_id: org.id, age: '1' })) + await petsRepository.create(makePet({ org_id: org.id, age: '2' })) + + const org2 = await orgsRepository.create(makeOrg({ city: 'city2' })); + await petsRepository.create(makePet({ org_id: org2.id, age: '1' })) + await petsRepository.create(makePet({ org_id: org2.id, age: '2' })) + + const { pets } = await sut.execute({ city: org.city, age: '1' }) + expect(pets).toHaveLength(2) + + const { pets: pets2 } = await sut.execute({ city: org2.city, age: '3' }) + expect(pets2).toHaveLength(0) + }); + + it("Deve ser possivel listar os pets de uma cidade filtrando por 'size'", async () => { + const org = await orgsRepository.create(makeOrg({ city: 'city1' })); + await petsRepository.create(makePet({ org_id: org.id, size: 'small' })) + await petsRepository.create(makePet({ org_id: org.id, size: 'small' })) + await petsRepository.create(makePet({ org_id: org.id, size: 'medium' })) + + const org2 = await orgsRepository.create(makeOrg({ city: 'city2' })); + await petsRepository.create(makePet({ org_id: org2.id, size: 'small' })) + await petsRepository.create(makePet({ org_id: org2.id, size: 'medium' })) + + const { pets } = await sut.execute({ city: org.city, size: 'small' }) + expect(pets).toHaveLength(2) + + const { pets: pets2 } = await sut.execute({ city: org2.city, size: 'large' }) + expect(pets2).toHaveLength(0) + }); + + it("Deve ser possivel listar os pets de uma cidade filtrando por 'energy_level'", async () => { + const org = await orgsRepository.create(makeOrg({ city: 'city1' })); + + await petsRepository.create(makePet({ org_id: org.id, energy_level: 'low' })) + await petsRepository.create(makePet({ org_id: org.id, energy_level: 'low' })) + await petsRepository.create(makePet({ org_id: org.id, energy_level: 'medium' })) + await petsRepository.create(makePet({ org_id: org.id, energy_level: 'high' })) + + const org2 = await orgsRepository.create(makeOrg({ city: 'city2' })); + await petsRepository.create(makePet({ org_id: org2.id, energy_level: 'low' })) + await petsRepository.create(makePet({ org_id: org2.id, energy_level: 'medium' })) + + const { pets } = await sut.execute({ city: org.city, energy_level: 'low' }) + expect(pets).toHaveLength(2) + + const { pets: pets2 } = await sut.execute({ city: org2.city, energy_level: 'high' }) + expect(pets2).toHaveLength(0) + }); + + it("Deve ser possivel listar os pets de uma cidade filtrando por 'environment'", async () => { + const org = await orgsRepository.create(makeOrg({ city: 'city1' })); + + await petsRepository.create(makePet({ org_id: org.id, environment: 'indoor' })) + await petsRepository.create(makePet({ org_id: org.id, environment: 'indoor' })) + + await petsRepository.create(makePet({ org_id: org.id, environment: 'outdoor' })) + + const { pets } = await sut.execute({ city: org.city, environment: 'indoor' }) + expect(pets).toHaveLength(2) + + }); + +}); diff --git a/src/use-cases/pets/listPetUseCase.ts b/src/use-cases/pets/listPetUseCase.ts new file mode 100644 index 0000000..cfb1c13 --- /dev/null +++ b/src/use-cases/pets/listPetUseCase.ts @@ -0,0 +1,30 @@ +import { IPetsRepository } from "@/repositories/IPetsRepository"; +import { Pets } from "prisma/generated/prisma"; +import { ResourceNotFoundError } from "../errors/resourceNotFound"; + +interface ListPetUseCaseRequest { + city: string; + age?: string; + size?: string; + energy_level?: string; + environment?: string; +} + +interface ListPetUseCaseResponse { + pets: Pets[] +} + +export class ListPetUseCase { + + constructor( + private petsRepository: IPetsRepository + ) { } + + async execute(data: ListPetUseCaseRequest): Promise { + const pets = await this.petsRepository.findAll({ ...data }) + + return { + pets + } + } +} \ No newline at end of file