Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand All @@ -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",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions prisma/migrations/20250711015614_create_pets/migration.sql
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 17 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
38 changes: 38 additions & 0 deletions src/http/controllers/pets/createPetsController.ts
Original file line number Diff line number Diff line change
@@ -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();
}
31 changes: 31 additions & 0 deletions src/http/controllers/pets/getPetsController.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
29 changes: 29 additions & 0 deletions src/http/controllers/pets/listPetsController.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
11 changes: 11 additions & 0 deletions src/http/controllers/pets/petsRoutes.ts
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 3 additions & 1 deletion src/http/routes.ts
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 1 addition & 1 deletion src/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ app.setErrorHandler((error, _, reply) => {
{
return reply.status(400).send({
message: 'Validation error',
issues: error.format(),
issues: error.issues,
});
}

Expand Down
15 changes: 15 additions & 0 deletions src/repositories/IPetsRepository.ts
Original file line number Diff line number Diff line change
@@ -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<Pets>
findById(id: string): Promise<Pets | null>
findAll(data: FindAllParams): Promise<Pets[]>
}
52 changes: 52 additions & 0 deletions src/repositories/in-memory/petsRepositoryInMemory.ts
Original file line number Diff line number Diff line change
@@ -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<Pets[]> {
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
}
}
41 changes: 41 additions & 0 deletions src/repositories/prisma/petsRepositoryPrisma.ts
Original file line number Diff line number Diff line change
@@ -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<Pets> {
const ret = await prisma.pets.create({ data });

return ret;
}

async findById(id: string): Promise<Pets | null> {
const ret = await prisma.pets.findUnique({
where: {
id
}
});

return ret;
}

async findAll(data: FindAllParams): Promise<Pets[]> {
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;
}

}
5 changes: 5 additions & 0 deletions src/use-cases/errors/orgNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class OrgNotFoundError extends Error {
constructor() {
super('Organization not found.')
}
}
11 changes: 11 additions & 0 deletions src/use-cases/factories/makeCreatePetsUseCase.ts
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 9 additions & 0 deletions src/use-cases/factories/makeGetPetsUseCase.ts
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 10 additions & 0 deletions src/use-cases/factories/makeListPetsUseCase.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading