From 104dc5e7f242d873891d11f0d6cac025e4f4a667 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Mon, 14 Jul 2025 16:32:32 -0300 Subject: [PATCH 01/11] refact: Colocar factories de users dentro de pasta users --- src/http/controllers/users/authenticate.ts | 2 +- src/http/controllers/users/getUser.ts | 2 +- src/http/controllers/users/list.ts | 2 +- src/http/controllers/users/register.ts | 2 +- src/http/controllers/users/remove.ts | 2 +- src/http/controllers/users/update.ts | 2 +- src/services/factories/{ => users}/make-authenticate-service.ts | 2 +- src/services/factories/{ => users}/make-get-user-service.ts | 0 src/services/factories/{ => users}/make-list-service.ts | 0 src/services/factories/{ => users}/make-register-service.ts | 2 +- src/services/factories/{ => users}/make-remove-service.ts | 2 +- src/services/factories/{ => users}/make-update-service.ts | 0 12 files changed, 9 insertions(+), 9 deletions(-) rename src/services/factories/{ => users}/make-authenticate-service.ts (82%) rename src/services/factories/{ => users}/make-get-user-service.ts (100%) rename src/services/factories/{ => users}/make-list-service.ts (100%) rename src/services/factories/{ => users}/make-register-service.ts (83%) rename src/services/factories/{ => users}/make-remove-service.ts (83%) rename src/services/factories/{ => users}/make-update-service.ts (100%) diff --git a/src/http/controllers/users/authenticate.ts b/src/http/controllers/users/authenticate.ts index 38c57e6..8f19a38 100644 --- a/src/http/controllers/users/authenticate.ts +++ b/src/http/controllers/users/authenticate.ts @@ -2,7 +2,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify' import { z } from 'zod' import { InvalidCredentialsError } from '@/services/errors/invalid-credentials-error' -import { makeAuthenticateService } from '@/services/factories/make-authenticate-service' +import { makeAuthenticateService } from '@/services/factories/users/make-authenticate-service' export async function authenticate( request: FastifyRequest, diff --git a/src/http/controllers/users/getUser.ts b/src/http/controllers/users/getUser.ts index e8008ef..56d55ae 100644 --- a/src/http/controllers/users/getUser.ts +++ b/src/http/controllers/users/getUser.ts @@ -1,6 +1,6 @@ import type { FastifyReply, FastifyRequest } from 'fastify' -import { makeGetUserService } from '@/services/factories/make-get-user-service' +import { makeGetUserService } from '@/services/factories/users/make-get-user-service' export async function getUser(request: FastifyRequest, reply: FastifyReply) { const getUser = makeGetUserService() diff --git a/src/http/controllers/users/list.ts b/src/http/controllers/users/list.ts index 123f860..64935d6 100644 --- a/src/http/controllers/users/list.ts +++ b/src/http/controllers/users/list.ts @@ -1,7 +1,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify' import { UserNotExistsError } from '@/services/errors/user-not-exists-error' -import { makeListService } from '@/services/factories/make-list-service' +import { makeListService } from '@/services/factories/users/make-list-service' export async function list(_: FastifyRequest, reply: FastifyReply) { let users = null diff --git a/src/http/controllers/users/register.ts b/src/http/controllers/users/register.ts index 5d78ecc..ed6aa9f 100644 --- a/src/http/controllers/users/register.ts +++ b/src/http/controllers/users/register.ts @@ -2,7 +2,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify' import { z } from 'zod' import { UserAlreadyExistsError } from '@/services/errors/user-already-exists-error' -import { makeRegisterService } from '@/services/factories/make-register-service' +import { makeRegisterService } from '@/services/factories/users/make-register-service' export async function register(request: FastifyRequest, reply: FastifyReply) { const registerBodySchema = z.object({ diff --git a/src/http/controllers/users/remove.ts b/src/http/controllers/users/remove.ts index b774e3c..b171749 100644 --- a/src/http/controllers/users/remove.ts +++ b/src/http/controllers/users/remove.ts @@ -1,7 +1,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify' import { UserNotExistsError } from '@/services/errors/user-not-exists-error' -import { makeRemoveService } from '@/services/factories/make-remove-service' +import { makeRemoveService } from '@/services/factories/users/make-remove-service' import { logout } from './logout' diff --git a/src/http/controllers/users/update.ts b/src/http/controllers/users/update.ts index 1dd9e91..28f44fb 100644 --- a/src/http/controllers/users/update.ts +++ b/src/http/controllers/users/update.ts @@ -2,7 +2,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify' import { z } from 'zod' import { UserNotExistsError } from '@/services/errors/user-not-exists-error' -import { makeUpdateService } from '@/services/factories/make-update-service' +import { makeUpdateService } from '@/services/factories/users/make-update-service' export async function update(request: FastifyRequest, reply: FastifyReply) { const updateBodySchema = z diff --git a/src/services/factories/make-authenticate-service.ts b/src/services/factories/users/make-authenticate-service.ts similarity index 82% rename from src/services/factories/make-authenticate-service.ts rename to src/services/factories/users/make-authenticate-service.ts index 39e288e..9fe52f1 100644 --- a/src/services/factories/make-authenticate-service.ts +++ b/src/services/factories/users/make-authenticate-service.ts @@ -1,5 +1,5 @@ import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' -import { AuthenticateService } from '../users/authenticate' +import { AuthenticateService } from '../../users/authenticate' export function makeAuthenticateService() { const usersRepository = new PrismaUsersRepository() diff --git a/src/services/factories/make-get-user-service.ts b/src/services/factories/users/make-get-user-service.ts similarity index 100% rename from src/services/factories/make-get-user-service.ts rename to src/services/factories/users/make-get-user-service.ts diff --git a/src/services/factories/make-list-service.ts b/src/services/factories/users/make-list-service.ts similarity index 100% rename from src/services/factories/make-list-service.ts rename to src/services/factories/users/make-list-service.ts diff --git a/src/services/factories/make-register-service.ts b/src/services/factories/users/make-register-service.ts similarity index 83% rename from src/services/factories/make-register-service.ts rename to src/services/factories/users/make-register-service.ts index 7505741..a3b1395 100644 --- a/src/services/factories/make-register-service.ts +++ b/src/services/factories/users/make-register-service.ts @@ -1,5 +1,5 @@ import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' -import { RegisterService } from '../users/register' +import { RegisterService } from '../../users/register' export function makeRegisterService() { const usersRepository = new PrismaUsersRepository() diff --git a/src/services/factories/make-remove-service.ts b/src/services/factories/users/make-remove-service.ts similarity index 83% rename from src/services/factories/make-remove-service.ts rename to src/services/factories/users/make-remove-service.ts index 921b66e..d5b33ae 100644 --- a/src/services/factories/make-remove-service.ts +++ b/src/services/factories/users/make-remove-service.ts @@ -1,5 +1,5 @@ import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' -import { RemoveService } from '../users/remove' +import { RemoveService } from '../../users/remove' export function makeRemoveService() { const usersRepository = new PrismaUsersRepository() diff --git a/src/services/factories/make-update-service.ts b/src/services/factories/users/make-update-service.ts similarity index 100% rename from src/services/factories/make-update-service.ts rename to src/services/factories/users/make-update-service.ts From 072915e23cc94cc150079d8c42cb7ff463ce0e28 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Thu, 24 Jul 2025 18:20:30 -0300 Subject: [PATCH 02/11] chore: Tornar campo zipCode Int e complement opcional --- .../migration.sql | 10 ++++++++++ prisma/schema.prisma | 10 +++++----- prisma/seed.ts | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 prisma/migrations/20250724210200_mudar_dois_campos_da_tabela_address/migration.sql diff --git a/prisma/migrations/20250724210200_mudar_dois_campos_da_tabela_address/migration.sql b/prisma/migrations/20250724210200_mudar_dois_campos_da_tabela_address/migration.sql new file mode 100644 index 0000000..a57fa57 --- /dev/null +++ b/prisma/migrations/20250724210200_mudar_dois_campos_da_tabela_address/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Changed the type of `zip_code` on the `addresses` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- AlterTable +ALTER TABLE "addresses" ALTER COLUMN "complement" DROP NOT NULL, +DROP COLUMN "zip_code", +ADD COLUMN "zip_code" INTEGER NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7b3b1e6..6849d5f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,15 +8,15 @@ datasource db { } model Address { - id String @id @default(cuid()) + id String @id @default(cuid()) number Int street String neighborhood String - complement String + complement String? city String - state String @default("SP") - country String @default("BR") - zipCode String @map("zip_code") + state String @default("SP") + country String @default("BR") + zipCode Int @map("zip_code") latitude Float longitude Float diff --git a/prisma/seed.ts b/prisma/seed.ts index 71da612..41e2d31 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -56,7 +56,7 @@ async function seed() { street: faker.location.street(), neighborhood: faker.location.secondaryAddress(), number: Number.parseInt(faker.location.buildingNumber()), - zipCode: faker.location.zipCode(), + zipCode: Number.parseInt(faker.location.zipCode()), latitude: faker.location.latitude(), longitude: faker.location.longitude(), userId: randomUser.id, From 5a07e5f26028e2c3ba698a885b9e1822b101c971 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 25 Jul 2025 18:48:48 -0300 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20Implementar=20reposit=C3=B3rios?= =?UTF-8?q?=20de=20address?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/repositories/address-repository.ts | 12 ++++ .../prisma/prisma-address-repository.ts | 58 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/repositories/address-repository.ts create mode 100644 src/repositories/prisma/prisma-address-repository.ts diff --git a/src/repositories/address-repository.ts b/src/repositories/address-repository.ts new file mode 100644 index 0000000..e212698 --- /dev/null +++ b/src/repositories/address-repository.ts @@ -0,0 +1,12 @@ +import type { Address, Prisma } from '@prisma/client' + +export interface AddressRepository { + create(userId: string, data: Prisma.AddressCreateInput): Promise
+ findByUserId(userId: string): Promise
+ updateByUserId( + userId: string, + data: Prisma.AddressUpdateInput, + ): Promise
+ removeByUserId(userId: string): Promise
+ list(): Promise +} diff --git a/src/repositories/prisma/prisma-address-repository.ts b/src/repositories/prisma/prisma-address-repository.ts new file mode 100644 index 0000000..5a5a2b7 --- /dev/null +++ b/src/repositories/prisma/prisma-address-repository.ts @@ -0,0 +1,58 @@ +import { prisma } from '@/lib/prisma' +import type { Prisma } from '@prisma/client' + +export class PrismaAddressRepository { + async create(userId: string, data: Prisma.AddressCreateInput) { + const address = await prisma.address.create({ + data: { + ...data, + user: { + connect: { id: userId }, + }, + }, + }) + + return address + } + + async findByUserId(userId: string) { + const address = await prisma.address.findFirst({ + where: { + userId, + }, + }) + + return address + } + + async updateByUserId(userId: string, data: Prisma.AddressUpdateInput) { + const address = await prisma.address.update({ + where: { + userId, + }, + data, + }) + + return address + } + + async list() { + const address = await prisma.address.findMany({ + orderBy: { + updatedAt: 'desc', + }, + }) + + return address + } + + async removeByUserId(userId: string) { + const address = await prisma.address.delete({ + where: { + userId, + }, + }) + + return address + } +} From 8388f483cfe7c00a27feeaae4ca1daa13b8ace3b Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 25 Jul 2025 18:50:26 -0300 Subject: [PATCH 04/11] feat: Criar classes de erros para address --- src/services/errors/address-already-exists-error.ts | 5 +++++ src/services/errors/address-not-exists-error.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/services/errors/address-already-exists-error.ts create mode 100644 src/services/errors/address-not-exists-error.ts diff --git a/src/services/errors/address-already-exists-error.ts b/src/services/errors/address-already-exists-error.ts new file mode 100644 index 0000000..26dab77 --- /dev/null +++ b/src/services/errors/address-already-exists-error.ts @@ -0,0 +1,5 @@ +export class AddressAlreadyExistsError extends Error { + constructor() { + super('Address already exists.') + } +} diff --git a/src/services/errors/address-not-exists-error.ts b/src/services/errors/address-not-exists-error.ts new file mode 100644 index 0000000..03d9a82 --- /dev/null +++ b/src/services/errors/address-not-exists-error.ts @@ -0,0 +1,5 @@ +export class AddressNotExistsError extends Error { + constructor() { + super('Address not exists.') + } +} From 666accf437006886659e56862fc27484fd2e6fef Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 25 Jul 2025 19:40:18 -0300 Subject: [PATCH 05/11] feat: Criar services de address --- src/services/addresses/get-address.ts | 20 ++++++++ src/services/addresses/list.ts | 11 ++++ src/services/addresses/register.ts | 73 +++++++++++++++++++++++++++ src/services/addresses/remove.ts | 22 ++++++++ src/services/addresses/update.ts | 27 ++++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/services/addresses/get-address.ts create mode 100644 src/services/addresses/list.ts create mode 100644 src/services/addresses/register.ts create mode 100644 src/services/addresses/remove.ts create mode 100644 src/services/addresses/update.ts diff --git a/src/services/addresses/get-address.ts b/src/services/addresses/get-address.ts new file mode 100644 index 0000000..4ed0d34 --- /dev/null +++ b/src/services/addresses/get-address.ts @@ -0,0 +1,20 @@ +import type { AddressRepository } from '@/repositories/address-repository' +import { AddressNotExistsError } from '../errors/address-not-exists-error' + +interface GetAddressServiceRequest { + userId: string +} + +export class GetAddressService { + constructor(private addressRepository: AddressRepository) {} + + async execute({ userId }: GetAddressServiceRequest) { + const address = await this.addressRepository.findByUserId(userId) + + if (!address) { + throw new AddressNotExistsError() + } + + return { address } + } +} diff --git a/src/services/addresses/list.ts b/src/services/addresses/list.ts new file mode 100644 index 0000000..38bf61d --- /dev/null +++ b/src/services/addresses/list.ts @@ -0,0 +1,11 @@ +import type { AddressRepository } from '@/repositories/address-repository' + +export class ListService { + constructor(private addressRepository: AddressRepository) {} + + async execute() { + const address = await this.addressRepository.list() + + return { address } + } +} diff --git a/src/services/addresses/register.ts b/src/services/addresses/register.ts new file mode 100644 index 0000000..ad44642 --- /dev/null +++ b/src/services/addresses/register.ts @@ -0,0 +1,73 @@ +import type { AddressRepository } from '@/repositories/address-repository' +import type { UsersRepository } from '@/repositories/users-repository' +import { AddressAlreadyExistsError } from '../errors/address-already-exists-error' +import { UserNotExistsError } from '../errors/user-not-exists-error' + +interface RegisterServiceRequest { + number: number + street: string + neighborhood: string + complement?: string + city: string + state?: string + country?: string + zipCode: number + latitude: number + longitude: number + + userId: string +} + +export class RegisterService { + constructor( + private addressRepository: AddressRepository, + private userRepository: UsersRepository, + ) {} + + async execute({ + number, + street, + neighborhood, + complement, + city, + state, + country, + zipCode, + latitude, + longitude, + userId, + }: RegisterServiceRequest) { + const doesUserExists = await this.userRepository.findById(userId) + + if (!doesUserExists) { + throw new UserNotExistsError() + } + + const doesAddressAlreadyExists = + await this.addressRepository.findByUserId(userId) + + if (doesAddressAlreadyExists) { + throw new AddressAlreadyExistsError() + } + + const address = await this.addressRepository.create(userId, { + number, + street, + neighborhood, + complement, + city, + state, + country, + zipCode, + latitude, + longitude, + user: { + connect: { + id: userId, + }, + }, + }) + + return { address } + } +} diff --git a/src/services/addresses/remove.ts b/src/services/addresses/remove.ts new file mode 100644 index 0000000..2c7a354 --- /dev/null +++ b/src/services/addresses/remove.ts @@ -0,0 +1,22 @@ +import type { AddressRepository } from '@/repositories/address-repository' +import { AddressNotExistsError } from '../errors/address-not-exists-error' + +interface RemoveServiceRequest { + userId: string +} + +export class RemoveService { + constructor(private addressRepository: AddressRepository) {} + + async execute({ userId }: RemoveServiceRequest) { + const doesAddressExists = await this.addressRepository.findByUserId(userId) + + if (!doesAddressExists) { + throw new AddressNotExistsError() + } + + const address = await this.addressRepository.removeByUserId(userId) + + return { address } + } +} diff --git a/src/services/addresses/update.ts b/src/services/addresses/update.ts new file mode 100644 index 0000000..034303f --- /dev/null +++ b/src/services/addresses/update.ts @@ -0,0 +1,27 @@ +import type { AddressRepository } from '@/repositories/address-repository' +import { AddressNotExistsError } from '../errors/address-not-exists-error' + +interface UpdateServiceRequest { + userId: string + data: { + zipCode?: number + number?: number + complement?: string + } +} + +export class UpdateService { + constructor(private addressRepository: AddressRepository) {} + + async execute({ userId, data }: UpdateServiceRequest) { + const doesAddressExists = await this.addressRepository.findByUserId(userId) + + if (!doesAddressExists) { + throw new AddressNotExistsError() + } + + const address = await this.addressRepository.updateByUserId(userId, data) + + return { address } + } +} From 3422254b26af88a5a42de95c3642876ed5bb0d49 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 25 Jul 2025 19:46:04 -0300 Subject: [PATCH 06/11] feat: Implementar factories de address --- .../addresses/make-get-address-service.ts | 9 +++++++++ .../factories/addresses/make-list-service.ts | 9 +++++++++ .../factories/addresses/make-register-service.ts | 15 +++++++++++++++ .../factories/addresses/make-remove-service.ts | 9 +++++++++ .../factories/addresses/make-update-service.ts | 9 +++++++++ 5 files changed, 51 insertions(+) create mode 100644 src/services/factories/addresses/make-get-address-service.ts create mode 100644 src/services/factories/addresses/make-list-service.ts create mode 100644 src/services/factories/addresses/make-register-service.ts create mode 100644 src/services/factories/addresses/make-remove-service.ts create mode 100644 src/services/factories/addresses/make-update-service.ts diff --git a/src/services/factories/addresses/make-get-address-service.ts b/src/services/factories/addresses/make-get-address-service.ts new file mode 100644 index 0000000..9b09a10 --- /dev/null +++ b/src/services/factories/addresses/make-get-address-service.ts @@ -0,0 +1,9 @@ +import { PrismaAddressRepository } from '@/repositories/prisma/prisma-address-repository' +import { GetAddressService } from '@/services/addresses/get-address' + +export function makeGetAddressService() { + const addressRepository = new PrismaAddressRepository() + const getAddressService = new GetAddressService(addressRepository) + + return getAddressService +} diff --git a/src/services/factories/addresses/make-list-service.ts b/src/services/factories/addresses/make-list-service.ts new file mode 100644 index 0000000..9677010 --- /dev/null +++ b/src/services/factories/addresses/make-list-service.ts @@ -0,0 +1,9 @@ +import { PrismaAddressRepository } from '@/repositories/prisma/prisma-address-repository' +import { ListService } from '@/services/addresses/list' + +export function makeListService() { + const addressRepository = new PrismaAddressRepository() + const listService = new ListService(addressRepository) + + return listService +} diff --git a/src/services/factories/addresses/make-register-service.ts b/src/services/factories/addresses/make-register-service.ts new file mode 100644 index 0000000..edbae20 --- /dev/null +++ b/src/services/factories/addresses/make-register-service.ts @@ -0,0 +1,15 @@ +import { PrismaAddressRepository } from '@/repositories/prisma/prisma-address-repository' +import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' +import { RegisterService } from '@/services/addresses/register' + +export function makeRegisterService() { + const addressRepository = new PrismaAddressRepository() + const usersRepository = new PrismaUsersRepository() + + const registerService = new RegisterService( + addressRepository, + usersRepository, + ) + + return registerService +} diff --git a/src/services/factories/addresses/make-remove-service.ts b/src/services/factories/addresses/make-remove-service.ts new file mode 100644 index 0000000..36888c7 --- /dev/null +++ b/src/services/factories/addresses/make-remove-service.ts @@ -0,0 +1,9 @@ +import { PrismaAddressRepository } from '@/repositories/prisma/prisma-address-repository' +import { RemoveService } from '@/services/addresses/remove' + +export function makeRemoveService() { + const addressRepository = new PrismaAddressRepository() + const removeService = new RemoveService(addressRepository) + + return removeService +} diff --git a/src/services/factories/addresses/make-update-service.ts b/src/services/factories/addresses/make-update-service.ts new file mode 100644 index 0000000..0d4c8b0 --- /dev/null +++ b/src/services/factories/addresses/make-update-service.ts @@ -0,0 +1,9 @@ +import { PrismaAddressRepository } from '@/repositories/prisma/prisma-address-repository' +import { UpdateService } from '@/services/addresses/update' + +export function makeUpdateService() { + const addressRepository = new PrismaAddressRepository() + const updateService = new UpdateService(addressRepository) + + return updateService +} From bf61d345fa9540bebaa000e87f9d8d68af318f01 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sat, 26 Jul 2025 12:02:22 -0300 Subject: [PATCH 07/11] feat: Criar utils de obter address pelo zipCode --- src/utils/get-address-by-zipcode.ts | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/utils/get-address-by-zipcode.ts diff --git a/src/utils/get-address-by-zipcode.ts b/src/utils/get-address-by-zipcode.ts new file mode 100644 index 0000000..fd5d65c --- /dev/null +++ b/src/utils/get-address-by-zipcode.ts @@ -0,0 +1,70 @@ +interface BrazilApiResponse { + cep: number + state: string + city: string + neighborhood: string + street: string + service: string + location: { + type: string + coordinates: { + latitude: number + longitude: number + } + } +} + +export interface AddressApiResponse { + latitudeApi: number + longitudeApi: number + streetApi: string + neighborhoodApi: string + cityApi: string + zipCodeApi: number +} + +export async function getAddressByZipCode( + zipCode: number, +): Promise { + let data: BrazilApiResponse + try { + const response = await fetch( + `https://brasilapi.com.br/api/cep/v2/${zipCode}`, + { + signal: AbortSignal.timeout(5000), + }, + ) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + data = await response.json() + } catch (err) { + throw new Error( + `Erro ao buscar dados do endereço: ${(err as Error).message}`, + ) + } + + const zipCodeApi = Number(data.cep) + const streetApi = data.street + const neighborhoodApi = data.neighborhood + const cityApi = data.city + const latitudeApi = Number(data.location.coordinates.latitude) + const longitudeApi = Number(data.location.coordinates.longitude) + + if (!latitudeApi || !longitudeApi) { + throw new Error( + 'Não foi possível obter as coordenadas para o CEP informado.', + ) + } + + return { + zipCodeApi, + streetApi, + neighborhoodApi, + cityApi, + latitudeApi, + longitudeApi, + } +} From bba90f44ecd4a88d125df110e4f314f3acec27a9 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sat, 26 Jul 2025 12:05:19 -0300 Subject: [PATCH 08/11] fix: Implementar tratativa correta para alterar o address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit antes não seria possível alterar o address, porque seria alterado somente o zipCode --- src/services/addresses/update.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/services/addresses/update.ts b/src/services/addresses/update.ts index 034303f..5f9b678 100644 --- a/src/services/addresses/update.ts +++ b/src/services/addresses/update.ts @@ -1,4 +1,5 @@ import type { AddressRepository } from '@/repositories/address-repository' +import type { AddressApiResponse } from '@/utils/get-address-by-zipcode' import { AddressNotExistsError } from '../errors/address-not-exists-error' interface UpdateServiceRequest { @@ -7,6 +8,7 @@ interface UpdateServiceRequest { zipCode?: number number?: number complement?: string + addressApi: AddressApiResponse | null } } @@ -20,7 +22,26 @@ export class UpdateService { throw new AddressNotExistsError() } - const address = await this.addressRepository.updateByUserId(userId, data) + let address = null + if (data.zipCode !== undefined && data.addressApi !== null) { + address = await this.addressRepository.updateByUserId(userId, { + zipCode: data.zipCode, + number: data.number, + complement: data.complement, + neighborhood: data.addressApi.neighborhoodApi, + street: data.addressApi.streetApi, + city: data.addressApi.cityApi, + latitude: data.addressApi.latitudeApi, + longitude: data.addressApi.longitudeApi, + }) + } + + if (data.zipCode === undefined) { + address = await this.addressRepository.updateByUserId(userId, { + number: data.number, + complement: data.complement, + }) + } return { address } } From 8d0f8eae4d1a7c0a18d737489232facef4248e51 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sat, 26 Jul 2025 15:17:05 -0300 Subject: [PATCH 09/11] feat: Criar utils de validar address --- src/utils/validate-address.ts | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/utils/validate-address.ts diff --git a/src/utils/validate-address.ts b/src/utils/validate-address.ts new file mode 100644 index 0000000..d4b2443 --- /dev/null +++ b/src/utils/validate-address.ts @@ -0,0 +1,41 @@ +import type { AddressApiResponse } from './get-address-by-zipcode' + +interface AddressUser { + street: string + neighborhood: string + city: string + zipCode: number + latitude: number + longitude: number +} + +const errorMessage = + 'Os dados fornecidos não correspondem ao endereço retornado pela API.' + +export function validateAddressFromApi( + { latitude, longitude, street, neighborhood, city, zipCode }: AddressUser, + { + zipCodeApi, + streetApi, + neighborhoodApi, + cityApi, + latitudeApi, + longitudeApi, + }: AddressApiResponse, +) { + if ( + zipCodeApi !== zipCode || + latitudeApi !== latitude || + longitudeApi !== longitude + ) { + throw new Error(errorMessage) + } + + if ( + streetApi.toLowerCase() !== street.toLowerCase() || + neighborhoodApi.toLowerCase() !== neighborhood.toLowerCase() || + cityApi.toLowerCase() !== city.toLowerCase() + ) { + throw new Error(errorMessage) + } +} From 9b876b6ab8840d8b0ed6de02c5dcf2d7c9b20135 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sat, 26 Jul 2025 15:23:28 -0300 Subject: [PATCH 10/11] feat: Criar controllers de address --- src/http/controllers/address/getAddress.ts | 23 ++++++ src/http/controllers/address/list.ts | 17 +++++ src/http/controllers/address/register.ts | 86 ++++++++++++++++++++++ src/http/controllers/address/remove.ts | 21 ++++++ src/http/controllers/address/update.ts | 79 ++++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 src/http/controllers/address/getAddress.ts create mode 100644 src/http/controllers/address/list.ts create mode 100644 src/http/controllers/address/register.ts create mode 100644 src/http/controllers/address/remove.ts create mode 100644 src/http/controllers/address/update.ts diff --git a/src/http/controllers/address/getAddress.ts b/src/http/controllers/address/getAddress.ts new file mode 100644 index 0000000..d42c727 --- /dev/null +++ b/src/http/controllers/address/getAddress.ts @@ -0,0 +1,23 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' + +import { AddressNotExistsError } from '@/services/errors/address-not-exists-error' +import { makeGetAddressService } from '@/services/factories/addresses/make-get-address-service' + +export async function getAddress(request: FastifyRequest, reply: FastifyReply) { + const userId = request.user.sub + + let address = null + try { + const getAddressService = makeGetAddressService() + + address = await getAddressService.execute({ userId }) + } catch (err) { + if (err instanceof AddressNotExistsError) { + return reply.status(404).send({ message: err.message }) + } + + throw err + } + + return reply.status(200).send(address) +} diff --git a/src/http/controllers/address/list.ts b/src/http/controllers/address/list.ts new file mode 100644 index 0000000..704c16d --- /dev/null +++ b/src/http/controllers/address/list.ts @@ -0,0 +1,17 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' + +import { makeListService } from '@/services/factories/addresses/make-list-service' + +export async function list(_: FastifyRequest, reply: FastifyReply) { + let addresses = null + try { + const listService = makeListService() + + addresses = await listService.execute() + } catch (err) { + // biome-ignore lint/complexity/noUselessCatch: + throw err + } + + return reply.status(200).send({ addresses: addresses.address }) +} diff --git a/src/http/controllers/address/register.ts b/src/http/controllers/address/register.ts new file mode 100644 index 0000000..f3d952a --- /dev/null +++ b/src/http/controllers/address/register.ts @@ -0,0 +1,86 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' +import { z } from 'zod' + +import { AddressAlreadyExistsError } from '@/services/errors/address-already-exists-error' +import { UserNotExistsError } from '@/services/errors/user-not-exists-error' +import { makeRegisterService } from '@/services/factories/addresses/make-register-service' +import { + type AddressApiResponse, + getAddressByZipCode, +} from '@/utils/get-address-by-zipcode' +import { validateAddressFromApi } from '@/utils/validate-address' + +export async function register(request: FastifyRequest, reply: FastifyReply) { + const registerBodySchema = z.object({ + number: z.number().int().nonnegative().min(100).max(99999), + street: z.string().nonempty(), + neighborhood: z.string().nonempty(), + complement: z.string().optional(), + city: z.string().nonempty(), + zipCode: z + .number() + .int() + .min(10000000) + .max(99999999) + .refine( + zip => { + const prefix = Number.parseInt(zip.toString().substring(0, 2)) + return prefix >= 1 && prefix <= 19 + }, + { + message: 'CEP não pertence ao estado de São Paulo', + }, + ), + latitude: z.number().min(-90).max(90).optional(), + longitude: z.number().min(-180).max(180).optional(), + }) + + const parsedBody = registerBodySchema.parse(request.body) + let { zipCode, latitude, longitude, street, neighborhood, city } = parsedBody + + let addressData: AddressApiResponse + try { + addressData = await getAddressByZipCode(zipCode) + } catch (err) { + return reply.status(400).send({ + message: (err as Error).message, + }) + } + + if (!latitude || !longitude) { + latitude = addressData.latitudeApi + longitude = addressData.longitudeApi + } + + try { + validateAddressFromApi( + { zipCode, street, neighborhood, city, latitude, longitude }, + addressData, + ) + } catch (err) { + return reply.status(400).send({ message: (err as Error).message }) + } + + try { + const registerService = makeRegisterService() + + await registerService.execute({ + ...parsedBody, + userId: request.user.sub, + latitude, + longitude, + }) + } catch (err) { + if (err instanceof UserNotExistsError) { + return reply.status(404).send({ message: err.message }) + } + + if (err instanceof AddressAlreadyExistsError) { + return reply.status(409).send({ message: err.message }) + } + + throw err + } + + return reply.status(201).send() +} diff --git a/src/http/controllers/address/remove.ts b/src/http/controllers/address/remove.ts new file mode 100644 index 0000000..3413b10 --- /dev/null +++ b/src/http/controllers/address/remove.ts @@ -0,0 +1,21 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' + +import { AddressNotExistsError } from '@/services/errors/address-not-exists-error' +import { makeRemoveService } from '@/services/factories/addresses/make-remove-service' + +export async function remove(request: FastifyRequest, reply: FastifyReply) { + const userId = request.user.sub + try { + const removeService = makeRemoveService() + + await removeService.execute({ userId }) + } catch (err) { + if (err instanceof AddressNotExistsError) { + return reply.status(400).send({ message: err.message }) + } + + throw err + } + + return reply.status(204).send() +} diff --git a/src/http/controllers/address/update.ts b/src/http/controllers/address/update.ts new file mode 100644 index 0000000..b1e5bfd --- /dev/null +++ b/src/http/controllers/address/update.ts @@ -0,0 +1,79 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' +import { z } from 'zod' + +import { AddressNotExistsError } from '@/services/errors/address-not-exists-error' +import { makeUpdateService } from '@/services/factories/addresses/make-update-service' +import { getAddressByZipCode } from '@/utils/get-address-by-zipcode' + +export async function update(request: FastifyRequest, reply: FastifyReply) { + const updateBodySchema = z + .object({ + zipCode: z + .number() + .int() + .min(10000000) + .max(99999999) + .refine( + zip => { + const prefix = Number.parseInt(zip.toString().substring(0, 2)) + return prefix >= 1 && prefix <= 19 + }, + { + message: 'CEP não pertence ao estado de São Paulo', + }, + ) + .optional(), + number: z.number().int().nonnegative().min(100).max(99999).optional(), + complement: z.string().optional(), + }) + .refine( + data => { + if (data.zipCode !== undefined && data.number === undefined) { + return false + } + + return ( + data.zipCode !== undefined || + data.number !== undefined || + data.complement !== undefined + ) + }, + { + message: + 'Se zipCode for fornecido, number também deve ser fornecido. Pelo menos um campo deve ser fornecido.', + }, + ) + + const userId = request.user.sub + const parsedBody = updateBodySchema.parse(request.body) + const { zipCode } = parsedBody + + let addressApi = null + try { + if (zipCode) { + addressApi = await getAddressByZipCode(zipCode) + } + } catch (err) { + return reply.status(400).send({ + message: (err as Error).message, + }) + } + + let address = null + try { + const updateService = makeUpdateService() + + address = await updateService.execute({ + userId, + data: { ...parsedBody, addressApi }, + }) + } catch (err) { + if (err instanceof AddressNotExistsError) { + return reply.status(404).send({ message: err.message }) + } + + throw err + } + + return reply.status(200).send(address) +} From 1bf6f77fdc80c2aa8b9c206634609ba61f898df7 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sat, 26 Jul 2025 15:24:51 -0300 Subject: [PATCH 11/11] feat: Implementar rotas de address --- src/http/controllers/address/routes.ts | 19 +++++++++++++++++++ src/http/controllers/routes.ts | 2 ++ 2 files changed, 21 insertions(+) create mode 100644 src/http/controllers/address/routes.ts diff --git a/src/http/controllers/address/routes.ts b/src/http/controllers/address/routes.ts new file mode 100644 index 0000000..f1fc52c --- /dev/null +++ b/src/http/controllers/address/routes.ts @@ -0,0 +1,19 @@ +import type { FastifyInstance } from 'fastify' + +import { verifyJWT } from '@/http/middlewares/verify-jwt' +import { verifyUserRole } from '@/http/middlewares/verify-user-role' + +import { getAddress } from './getAddress' +import { list } from './list' +import { register } from './register' +import { remove } from './remove' +import { update } from './update' + +export async function addressRoutes(app: FastifyInstance) { + app.post('/address', { onRequest: [verifyJWT] }, register) + app.get('/address', { onRequest: [verifyJWT] }, getAddress) + app.patch('/address', { onRequest: [verifyJWT] }, update) + app.delete('/address', { onRequest: [verifyJWT] }, remove) + + app.get('/addresses', { onRequest: [verifyUserRole('ADMIN')] }, list) +} diff --git a/src/http/controllers/routes.ts b/src/http/controllers/routes.ts index 99fc169..c942564 100644 --- a/src/http/controllers/routes.ts +++ b/src/http/controllers/routes.ts @@ -1,7 +1,9 @@ import type { FastifyInstance } from 'fastify' +import { addressRoutes } from './address/routes' import { usersRoutes } from './users/routes' export async function appRoutes(app: FastifyInstance) { app.register(usersRoutes) + app.register(addressRoutes) }