Skip to content
Open
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 createSoloRoutes } from './solo'

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),
...createSoloRoutes(knex)
]
const application = new ExpressApplication({ logger })

Expand Down
41 changes: 41 additions & 0 deletions src/application/solo/BuscarSoloController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { BuscarSoloPorIdUseCase } from '@/domain/solo/BuscarSoloPorIdUseCase'
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 {
buscarSoloPorIdUseCase: BuscarSoloPorIdUseCase
}

export class BuscarSoloController implements RequestHandler {
private readonly buscarSoloPorIdUseCase: BuscarSoloPorIdUseCase

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

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

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

const result = await this.buscarSoloPorIdUseCase.execute({ id: Number(soloId) })

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

if (!result.value) {
return new NotFoundError({ message: 'Solo não encontrado' })
}

return { statusCode: StatusCode.Ok, body: result.value }
}
}
65 changes: 65 additions & 0 deletions src/application/solo/ListaSolosController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ListaSolosUseCase } from '@/domain/solo/ListaSolosUseCase'
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 {
listaSolosUseCase: ListaSolosUseCase
}

export class ListaSolosController implements RequestHandler {
private readonly listaSolosUseCase: ListaSolosUseCase

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

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.listaSolosUseCase.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/solo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Knex } from 'knex'

import { BuscarSoloPorIdUseCase } from '@/domain/solo/BuscarSoloPorIdUseCase'
import { ListaSolosUseCase } from '@/domain/solo/ListaSolosUseCase'
import { SoloCollectionKnexAdapter } from '@/infrastructure/SoloCollectionKnexAdapter'
import { Method } from '@/library/http/common'
import { Route } from '@/library/http/Router'

import { BuscarSoloController } from './BuscarSoloController'
import { ListaSolosController } from './ListaSolosController'

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

return [
{
handlers: [
new ListaSolosController({
listaSolosUseCase: new ListaSolosUseCase({ soloCollection })
})
],
method: Method.Get,
path: '/v2/solos'
},
{
handlers: [
new BuscarSoloController({
buscarSoloPorIdUseCase: new BuscarSoloPorIdUseCase({ soloCollection })
})
],
method: Method.Get,
path: '/v2/solos/:soloId'
}
]
}
20 changes: 20 additions & 0 deletions src/domain/solo/BuscarSoloPorIdUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Either } from '@/library/either/Either'

import { Attributes } from './Solo'
import { SoloCollection } from './SoloCollection'

interface Dependencies {
soloCollection: SoloCollection
}

export class BuscarSoloPorIdUseCase {
private readonly soloCollection: SoloCollection

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

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

import { Attributes } from './Solo'
import { SoloCollection, SoloFilters } from './SoloCollection'

interface Dependencies {
soloCollection: SoloCollection
}

export class ListaSolosUseCase {
private readonly soloCollection: SoloCollection

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

execute(filters: SoloFilters): Promise<Either<Error, Attributes[]>> {
return this.soloCollection.findAll(filters)
}
}
24 changes: 24 additions & 0 deletions src/domain/solo/Solo.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 Solo {
readonly id: number
readonly nome: string

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

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

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

import { Attributes } from './Solo'

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

export interface SoloFilters {
nome?: string
order?: SoloOrder
}

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

import { Attributes } from '@/domain/solo/Solo'
import { SoloCollection, SoloFilters } from '@/domain/solo/SoloCollection'
import { Either } from '@/library/either/Either'

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

interface Dependencies {
knex: Knex
}

export class SoloCollectionKnexAdapter implements SoloCollection {
private readonly knex: Knex

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

async findAll(filters: SoloFilters): Promise<Either<Error, Attributes[]>> {
try {
const query = this.knex<Attributes>('solos')
.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 solos', cause: error }))
}
}

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