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
4 changes: 3 additions & 1 deletion src/application/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import legacyErrors from '../middlewares/erros-middleware'
import { generatePreview, reportPreview } from '../reports/controller'
import { routes as createEstadoRoutes } from './estado'
import { routes as createPaisRoutes } from './pais'
import { routes as createVegetacaoRoutes } from './vegetacao'

interface CorsParameters {
origins: string[]
Expand Down Expand Up @@ -55,7 +56,8 @@ export function createApp({
}: Parameters) {
const routes: Route[] = [
...createPaisRoutes(knex),
...createEstadoRoutes(knex)
...createEstadoRoutes(knex),
...createVegetacaoRoutes(knex)
]
const application = new ExpressApplication({ logger })

Expand Down
41 changes: 41 additions & 0 deletions src/application/vegetacao/BuscarVegetacaoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { BuscarVegetacaoPorIdUseCase } from '@/domain/vegetacao/BuscarVegetacaoPorIdUseCase'
import {
HttpRequest, HttpResponse, StatusCode
} from '@/library/http/common'
import { BadRequestError } from '@/library/http/error/BadRequestError'
import { HttpError } from '@/library/http/error/HttpError'
import { InternalServerError } from '@/library/http/error/InternalServerError'
import { NotFoundError } from '@/library/http/error/NotFoundError'
import { NextHandler, RequestHandler } from '@/library/http/Server'

interface Dependencies {
buscarVegetacaoPorIdUseCase: BuscarVegetacaoPorIdUseCase
}

export class BuscarVegetacaoController implements RequestHandler {
private readonly buscarVegetacaoPorIdUseCase: BuscarVegetacaoPorIdUseCase

constructor(dependencies: Dependencies) {
this.buscarVegetacaoPorIdUseCase = dependencies.buscarVegetacaoPorIdUseCase
}

async handle(request: HttpRequest, _next: NextHandler): Promise<HttpResponse | HttpError> {
const { vegetacaoId } = request.params as { vegetacaoId?: string }

if (vegetacaoId === undefined || vegetacaoId === null || vegetacaoId === '' || !/^\d+$/.test(vegetacaoId)) {
return new BadRequestError({ message: 'vegetacaoId inválido' })
}

const result = await this.buscarVegetacaoPorIdUseCase.execute({ id: Number(vegetacaoId) })

if (result.left()) {
return new InternalServerError({ message: result.value.message })
}

if (!result.value) {
return new NotFoundError({ message: 'Vegetação não encontrada' })
}

return { statusCode: StatusCode.Ok, body: result.value }
}
}
65 changes: 65 additions & 0 deletions src/application/vegetacao/ListaVegetacoesController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ListaVegetacoesUseCase } from '@/domain/vegetacao/ListaVegetacoesUseCase'
import {
HttpRequest, HttpResponse, StatusCode
} from '@/library/http/common'
import { BadRequestError } from '@/library/http/error/BadRequestError'
import { HttpError } from '@/library/http/error/HttpError'
import { InternalServerError } from '@/library/http/error/InternalServerError'
import { NextHandler, RequestHandler } from '@/library/http/Server'

interface Dependencies {
listaVegetacoesUseCase: ListaVegetacoesUseCase
}

export class ListaVegetacoesController implements RequestHandler {
private readonly listaVegetacoesUseCase: ListaVegetacoesUseCase

constructor(dependencies: Dependencies) {
this.listaVegetacoesUseCase = dependencies.listaVegetacoesUseCase
}

async handle(request: HttpRequest, _next: NextHandler): Promise<HttpResponse | HttpError> {
const { nome, order } = request.params as {
nome?: string
order?: string
}

const parsedOrder = parseOrder(order)
if (parsedOrder instanceof Error) {
return new BadRequestError({ message: parsedOrder.message })
}

const result = await this.listaVegetacoesUseCase.execute({
nome,
order: parsedOrder
})

if (result.left()) {
return new InternalServerError({ message: result.value.message })
}

return { statusCode: StatusCode.Ok, body: result.value }
}
}

function parseOrder(order?: string): { column: 'id' | 'nome'; direction: 'asc' | 'desc' } | Error | undefined {
if (!order) return undefined

const pieces = order.split(':')
if (pieces.length !== 2) {
return new Error('order inválido. Use o formato "id:asc", "id:desc", "nome:asc" ou "nome:desc"')
}

const [rawColumn, rawDirection] = pieces
const column = rawColumn.trim().toLowerCase()
const direction = rawDirection.trim().toLowerCase()

if ((column !== 'id' && column !== 'nome') || (direction !== 'asc' && direction !== 'desc')) {
return new Error('order inválido. Use o formato "id:asc", "id:desc", "nome:asc" ou "nome:desc"')
}

return {
column,
direction
}
}
35 changes: 35 additions & 0 deletions src/application/vegetacao/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Knex } from 'knex'

import { BuscarVegetacaoPorIdUseCase } from '@/domain/vegetacao/BuscarVegetacaoPorIdUseCase'
import { ListaVegetacoesUseCase } from '@/domain/vegetacao/ListaVegetacoesUseCase'
import { VegetacaoCollectionKnexAdapter } from '@/infrastructure/VegetacaoCollectionKnexAdapter'
import { Method } from '@/library/http/common'
import { Route } from '@/library/http/Router'

import { BuscarVegetacaoController } from './BuscarVegetacaoController'
import { ListaVegetacoesController } from './ListaVegetacoesController'

export function routes(knex: Knex): Route[] {
const vegetacaoCollection = new VegetacaoCollectionKnexAdapter({ knex })

return [
{
handlers: [
new ListaVegetacoesController({
listaVegetacoesUseCase: new ListaVegetacoesUseCase({ vegetacaoCollection })
})
],
method: Method.Get,
path: '/v2/vegetacoes'
},
{
handlers: [
new BuscarVegetacaoController({
buscarVegetacaoPorIdUseCase: new BuscarVegetacaoPorIdUseCase({ vegetacaoCollection })
})
],
method: Method.Get,
path: '/v2/vegetacoes/:vegetacaoId'
}
]
}
20 changes: 20 additions & 0 deletions src/domain/vegetacao/BuscarVegetacaoPorIdUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Either } from '@/library/either/Either'

import { Attributes } from './Vegetacao'
import { VegetacaoCollection } from './VegetacaoCollection'

interface Dependencies {
vegetacaoCollection: VegetacaoCollection
}

export class BuscarVegetacaoPorIdUseCase {
private readonly vegetacaoCollection: VegetacaoCollection

constructor(dependencies: Dependencies) {
this.vegetacaoCollection = dependencies.vegetacaoCollection
}

execute({ id }: { id: number }): Promise<Either<Error, Attributes | null>> {
return this.vegetacaoCollection.findById(id)
}
}
20 changes: 20 additions & 0 deletions src/domain/vegetacao/ListaVegetacoesUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Either } from '@/library/either/Either'

import { Attributes } from './Vegetacao'
import { VegetacaoCollection, VegetacaoFilters } from './VegetacaoCollection'

interface Dependencies {
vegetacaoCollection: VegetacaoCollection
}

export class ListaVegetacoesUseCase {
private readonly vegetacaoCollection: VegetacaoCollection

constructor(dependencies: Dependencies) {
this.vegetacaoCollection = dependencies.vegetacaoCollection
}

execute(filters: VegetacaoFilters): Promise<Either<Error, Attributes[]>> {
return this.vegetacaoCollection.findAll(filters)
}
}
24 changes: 24 additions & 0 deletions src/domain/vegetacao/Vegetacao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Either } from '@/library/either/Either'

export interface Attributes {
id: number
nome: string
}

export class Vegetacao {
readonly id: number
readonly nome: string

private constructor(attributes: Attributes) {
this.id = attributes.id
this.nome = attributes.nome
}

static create(attributes: Attributes): Either<Error, Vegetacao> {
if (!attributes.nome.trim()) {
return Either.left(new Error('Nome da vegetação não pode ser vazio'))
}

return Either.right(new Vegetacao(attributes))
}
}
18 changes: 18 additions & 0 deletions src/domain/vegetacao/VegetacaoCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Either } from '@/library/either/Either'

import { Attributes } from './Vegetacao'

export interface VegetacaoOrder {
column: 'id' | 'nome'
direction: 'asc' | 'desc'
}

export interface VegetacaoFilters {
nome?: string
order?: VegetacaoOrder
}

export interface VegetacaoCollection {
findAll(filters: VegetacaoFilters): Promise<Either<Error, Attributes[]>>
findById(id: number): Promise<Either<Error, Attributes | null>>
}
46 changes: 46 additions & 0 deletions src/infrastructure/VegetacaoCollectionKnexAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Knex } from 'knex'

import { Attributes } from '@/domain/vegetacao/Vegetacao'
import { VegetacaoCollection, VegetacaoFilters } from '@/domain/vegetacao/VegetacaoCollection'
import { Either } from '@/library/either/Either'

import { CollectionError } from './error/CollectionError'

interface Dependencies {
knex: Knex
}

export class VegetacaoCollectionKnexAdapter implements VegetacaoCollection {
private readonly knex: Knex

constructor(dependencies: Dependencies) {
this.knex = dependencies.knex
}

async findAll(filters: VegetacaoFilters): Promise<Either<Error, Attributes[]>> {
try {
const query = this.knex<Attributes>('vegetacoes')
.select(['id', 'nome'])

if (filters.nome) {
query.whereILike('nome', `%${filters.nome}%`)
}

const order = filters.order ?? { column: 'id', direction: 'desc' }
query.orderBy(order.column, order.direction)

return Either.right(await query)
} catch (error) {
return Either.left(new CollectionError({ message: 'Failed to list vegetações', cause: error }))
}
}

async findById(id: number): Promise<Either<Error, Attributes | null>> {
try {
const vegetacao = await this.knex<Attributes>('vegetacoes').select(['id', 'nome']).where({ id }).first()
return Either.right(vegetacao ?? null)
} catch (error) {
return Either.left(new CollectionError({ message: 'Failed to find vegetação', cause: error }))
}
}
}
Loading
Loading