Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
0efe830
feat: implementa coleções Evidencia e Expedição
EduTiyo Sep 16, 2026
b0695c3
feat: eventos - endpoints de escrita (POST/PUT/DELETE)
vitorhugomoraes2486 Sep 20, 2026
23900b3
test: adicionado testes de dominio faltantes
vitorhugomoraes2486 Sep 20, 2026
86c349c
feat(expedicao): adiciona endpoint de cadastro
JordanBonfim Sep 20, 2026
ab6192d
feat(expedicao): adiciona endpoint de listagem com filtros
JordanBonfim Sep 20, 2026
a50c225
feat(expedicao): adiciona endpoint de busca por id
JordanBonfim Sep 20, 2026
c46de0d
feat(expedicao): endpoint de listagem com paginacao
JordanBonfim Sep 21, 2026
d5529ee
feat(expedicao): adiciona endpoint de exclusão
JordanBonfim Sep 21, 2026
3467048
feat(expedicao): adiciona endpoint de atualização
JordanBonfim Sep 21, 2026
b391863
Update AtualizarEventoController.ts
vitorhugomoraes2486 Sep 21, 2026
432d71a
Update CriarEventoController.ts
vitorhugomoraes2486 Sep 21, 2026
0196a7b
Update AtualizarEventoUseCase.ts
vitorhugomoraes2486 Sep 21, 2026
386cdfc
fix(expedicao): atualiza teste de integracao para o novo formato pagi…
JordanBonfim Sep 21, 2026
7ba74b1
fix(expedicao): corrige problemas de lint
JordanBonfim Sep 21, 2026
9e9979b
feat: implementa leitura de eventos
MariaGabriela-JR Sep 21, 2026
9fae20a
fix(expedicao): corrige mapeamento de erros 404 e 500 na busca por id
JordanBonfim Sep 21, 2026
a45d40f
fix(expedicao): cria ExpedicaoListItem para o findAll
JordanBonfim Sep 21, 2026
8abfacf
feat: interfaces detalhadas para expedicao, participante, e rota
DimitriGaraluz Sep 21, 2026
7169c03
fix(expedicao): implementa padrao de order e validacao no controller …
JordanBonfim Sep 21, 2026
1e33c0d
feat: adiciona contratos de participantes e rotas em ExpedicaoCollection
DimitriGaraluz Sep 21, 2026
83c9384
test(expedicao): cobre paginacao, ordenacao e filtros no findAll
JordanBonfim Sep 21, 2026
dd40c2b
fix(exception):tratamento de excecao endpoint de exclusao
JordanBonfim Sep 21, 2026
b6aa2fc
fix(expedicao): corrige verificação de erro na exclusão
JordanBonfim Sep 22, 2026
805699c
fix(expedicao): corrige mapeamento de erros no cadastro
JordanBonfim Sep 22, 2026
cce955e
fix(expedicao): corrige mapeamento de erros na atualizacao
JordanBonfim Sep 22, 2026
95b2897
feat: implementa gerenciamento de evidencia com upload e get endpoints
EduTiyo Sep 22, 2026
f700fde
refactor: update nas interfaces de ExpedicaoCollection por consistencia
DimitriGaraluz Sep 22, 2026
cb332b9
feat: adiciona use cases para adicionar e remover participantes, e, s…
DimitriGaraluz Sep 23, 2026
c8c09e7
feat: adciona ConflictError para http 409
DimitriGaraluz Sep 23, 2026
7de2280
feat: adiciona factory para ExpedicaoCollection
DimitriGaraluz Sep 23, 2026
b2dacef
feat: adiciona controllers e rotas para participantes e rotas
DimitriGaraluz Sep 23, 2026
8ee9b9b
feat: adciona metodos para participantes e rotas, e, ajusta findId e …
DimitriGaraluz Sep 23, 2026
6909a15
test: adiciona testes de integracao para participantes e rotas
DimitriGaraluz Sep 23, 2026
b45e95f
test: atualiza teste para refletir interface de retorno paginado
DimitriGaraluz Sep 23, 2026
cee01ea
fix: corrige formatacao e erros do lint
DimitriGaraluz Sep 23, 2026
d72b1f8
Merge pull request #540 from utfpr/crud-expedicoes
EduTiyo Sep 23, 2026
946e912
feat(expedicao): adiciona nome dos participantes no findAll
EduTiyo Sep 23, 2026
63f0842
refactor: atende revisão da PR
vitorhugomoraes2486 Sep 24, 2026
f534410
fix: resolve conflitos de merge
DimitriGaraluz Sep 24, 2026
ed1d519
fix: lint reafctor
DimitriGaraluz Sep 24, 2026
027f118
fix: ajusta leitura de eventos conforme review
MariaGabriela-JR Sep 24, 2026
27ad1f6
Merge branch '532-cadastro-expedicoes' into feature/eventos-escrita
EduTiyo Sep 24, 2026
f689219
Merge branch '532-cadastro-expedicoes' into feature/eventos-leitura
EduTiyo Sep 24, 2026
44eb344
Merge pull request #542 from utfpr/feature/eventos-escrita
EduTiyo Sep 24, 2026
878b21e
Merge branch '532-cadastro-expedicoes' into feature/eventos-leitura
EduTiyo Sep 24, 2026
7fc16b8
Merge pull request #541 from utfpr/feature/eventos-leitura
EduTiyo Sep 24, 2026
d36726b
fix: alinhar versão do TypeScript com o projeto
ArthurS07 Sep 25, 2026
54724fb
fix: corrige inconsistencia com "SubstitiuiRotasController"
DimitriGaraluz Sep 25, 2026
2f9f3d6
fix: remove interface duplicada ExpedicaoListItem
DimitriGaraluz Sep 25, 2026
3efcf58
refactor: implementa DuplicateParticipantError para tratamento do 409
DimitriGaraluz Sep 25, 2026
66466f3
refactor: altera mapeamento de erro para usar error.name e corrige o …
DimitriGaraluz Sep 25, 2026
f8d2b62
feat: implementa DELETE evidencias
ArthurS07 Sep 26, 2026
94e3489
fix: remove arquivos órfãos após ON DELETE CASCADE de evento ou exped…
ArthurS07 Sep 26, 2026
59c6af0
test: cobre remoção de arquivos de evidências no RemoverEventoUseCase
ArthurS07 Sep 26, 2026
7872c9e
Merge pull request #543 from utfpr/feature/Participates-e-rotas
EduTiyo Sep 27, 2026
921f9f0
fix(evidencia): registra rota DELETE /v2/evidencias/:evidenciaId
EduTiyo Sep 27, 2026
a5a7738
fix(build): remove artefatos locais de Yarn Berry commitados por engano
EduTiyo Sep 27, 2026
113d11c
fix(http): remove vazamento do tipo express da camada agnóstica de fr…
EduTiyo Sep 27, 2026
0ebcbe8
fix(evidencia): loga falha ao remover arquivo órfão em vez de engolir…
EduTiyo Sep 27, 2026
decbd0d
fix(evidencia): reusa EVIDENCIA_DIR em vez de path hardcoded para lim…
EduTiyo Sep 27, 2026
562ec80
Merge branch '532-cadastro-expedicoes' into feature/evidencia
EduTiyo Sep 27, 2026
b70b757
style: corrige formatação (indentação, espaçamento) apontada pelo eslint
EduTiyo Sep 27, 2026
0213550
style: corrige formatação apontada pelo eslint em evidencia/evento
EduTiyo Sep 27, 2026
94a71a0
Merge pull request #548 from utfpr/feature/evidencia
EduTiyo Sep 27, 2026
89ff196
Merge branch 'development' into 532-cadastro-expedicoes
EduTiyo Sep 27, 2026
09cfd26
fix: remove rotas duplicadas em create-app
EduTiyo Sep 27, 2026
1f58c7c
integração com a api
tiagodefendi Sep 28, 2026
a3fcfda
Merge branch 'development' into 532-cadastro-expedicoes
J0aoSiqueira Sep 28, 2026
3c16744
Merge branch 'development' into 532-cadastro-expedicoes
EduTiyo Sep 29, 2026
6f61587
feat(expedicao): adiciona filtros de data_fim_de e data_inicio_ate na…
EduTiyo Oct 4, 2026
61cc364
adição de lembretes
tiagodefendi Oct 5, 2026
1868350
Merge branch '532-cadastro-expedicoes' of https://github.com/utfpr/hc…
J0aoSiqueira Oct 5, 2026
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CORS_METHODS=HEAD,GET,POST,PUT,PATCH,DELETE
CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Requested-With

STORAGE_PATH=./uploads
EVIDENCIA_MAX_FILE_SIZE_MB=200

RECAPTCHA_SECRET_KEY=6LcYYYYYYYYYYYYYY

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ dist/
coverage/
node_modules/

# Yarn Berry local state — este projeto usa Yarn Classic (yarn lockfile v1)
.yarnrc.yml
.yarn/

uploads/
mysql/data/
postgresql/
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"liveServer.settings.port": 5501,
"[javascript]": {
"editor.tabSize": 4
}
},
"js/ts.tsdk.path": "./node_modules/typescript/lib"
}
2 changes: 2 additions & 0 deletions compose.integration.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
name: hcf-api-test

services:
postgres:
image: postgis/postgis:18-3.6
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@types/express": "5.0.5",
"@types/jsonwebtoken": "9.0.10",
"@types/morgan": "1.9.10",
"@types/multer": "^2.2.0",
"@types/pg": "^8.16.0",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
Expand Down
10 changes: 9 additions & 1 deletion src/application/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import { assets, upload } from '../config/directory'
import legacyErrors from '../middlewares/erros-middleware'
import { generatePreview, reportPreview } from '../reports/controller'
import { routes as createEstadoRoutes } from './estado'
import { routes as createEventoRoutes } from './evento'
import { routes as createEvidenciaRoutes } from './evidencia'
import { routes as createExpedicoesRoutes } from './expedicao'
import { routes as createFaseSucessionalRoutes } from './fase-sucessional'
import { routes as createLembreteRoutes } from './lembrete'
import { routes as createPaisRoutes } from './pais'
import { assertCookieSafeOrigins } from './parseCorsOrigins'
import { routes as createRelevoRoutes } from './relevo'
Expand Down Expand Up @@ -62,10 +66,14 @@ export function createApp({
const routes: Route[] = [
...createPaisRoutes(knex),
...createEstadoRoutes(knex),
...createEventoRoutes(knex),
...createSoloRoutes(knex),
...createRelevoRoutes(knex),
...createFaseSucessionalRoutes(knex),
...createVegetacaoRoutes(knex),
...createEvidenciaRoutes(knex),
...createExpedicoesRoutes(knex),
...createLembreteRoutes(knex),
...createUsuarioSessaoRoutes(knex)
]
const origins = assertCookieSafeOrigins(cors.origins)
Expand Down Expand Up @@ -122,7 +130,7 @@ export function createApp({

for (const route of routes) {
const sanitizedPath = `/api/${route.path}`.replaceAll(/\/{2,}/g, '/').replaceAll(/\/$/g, '')
application.endpoint(route.method, sanitizedPath, ...route.handlers)
application.endpoint(route.method, sanitizedPath, route.handlers, route.middlewares)
}

if (legacyRouter) {
Expand Down
153 changes: 153 additions & 0 deletions src/application/evento/AtualizarEventoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { AtualizarEventoUseCase, Input as AtualizarEventoInput } from '@/domain/evento/AtualizarEventoUseCase'
import { EVENTO_TIPOS, EventoTipo } from '@/domain/evento/Evento'
import { CheckViolationError } from '@/infrastructure/error/CheckViolationError'
import { ForeignKeyViolationError } from '@/infrastructure/error/ForeignKeyViolationError'
import { InfrastructureError } from '@/infrastructure/error/InfrastructureError'
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 { UnprocessableEntityError } from '@/library/http/error/UnprocessableEntityError'
import { NextHandler, RequestHandler } from '@/library/http/Server'

import { parseColeta } from './coleta-parsing'

interface Dependencies {
atualizarEventoUseCase: AtualizarEventoUseCase
}

interface Body {
tipo?: unknown
capturado_em?: unknown
latitude?: unknown
longitude?: unknown
altitude?: unknown
observacoes?: unknown
coleta?: unknown
}

export class AtualizarEventoController implements RequestHandler {
private readonly atualizarEventoUseCase: AtualizarEventoUseCase

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

async handle(request: HttpRequest, _next: NextHandler): Promise<HttpResponse | HttpError> {
const { eventoId: rawEventoId } = request.params as { eventoId?: string }
const eventoId = parseId(rawEventoId, 'eventoId')
if (eventoId instanceof Error) return new BadRequestError({ message: eventoId.message })

const body = (request.body ?? {}) as Body

// Substituir por request.usuario.id assim que a
// autenticação for integrada.
const input: AtualizarEventoInput = { id: eventoId, updated_by: null }

if (body.tipo !== undefined) {
const tipo = parseTipo(body.tipo)
if (tipo instanceof Error) return new BadRequestError({ message: tipo.message })
input.tipo = tipo
}

if (body.capturado_em !== undefined) {
const capturadoEm = parseDate(body.capturado_em, 'capturado_em')
if (capturadoEm instanceof Error) return new BadRequestError({ message: capturadoEm.message })
input.capturado_em = capturadoEm
}

if (body.latitude !== undefined) {
const latitude = parseOptionalNumber(body.latitude, 'latitude')
if (latitude instanceof Error) return new BadRequestError({ message: latitude.message })
input.latitude = latitude
}

if (body.longitude !== undefined) {
const longitude = parseOptionalNumber(body.longitude, 'longitude')
if (longitude instanceof Error) return new BadRequestError({ message: longitude.message })
input.longitude = longitude
}

if (body.altitude !== undefined) {
const altitude = parseOptionalNumber(body.altitude, 'altitude')
if (altitude instanceof Error) return new BadRequestError({ message: altitude.message })
input.altitude = altitude
}

if (body.observacoes !== undefined) {
const observacoes = parseOptionalString(body.observacoes, 'observacoes')
if (observacoes instanceof Error) return new BadRequestError({ message: observacoes.message })
input.observacoes = observacoes
}

if (body.coleta !== undefined) {
const coleta = body.coleta === null ? null : parseColeta(body.coleta)
if (coleta instanceof Error) return new BadRequestError({ message: coleta.message })
input.coleta = coleta
}

const result = await this.atualizarEventoUseCase.execute(input)

if (result.left()) {
const error = result.value
if (error instanceof ForeignKeyViolationError) return new NotFoundError({ message: error.message })
if (error instanceof CheckViolationError) return new UnprocessableEntityError({ message: error.message })
if (!(error instanceof InfrastructureError)) return new BadRequestError({ message: error.message })
return new InternalServerError({ message: error.message })
}

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

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

function parseId(raw: unknown, field: string): number | Error {
if (typeof raw !== 'string' || !/^\d+$/.test(raw)) {
return new Error(`${field} inválido`)
}
return Number(raw)
}

function parseTipo(raw: unknown): EventoTipo | Error {
if (typeof raw !== 'string' || !EVENTO_TIPOS.includes(raw as EventoTipo)) {
return new Error(`tipo inválido. Use um de: ${EVENTO_TIPOS.join(', ')}`)
}
return raw as EventoTipo
}

function parseDate(raw: unknown, field: string): Date | Error {
if (typeof raw !== 'string') {
return new Error(`${field} inválido. Use uma data no formato ISO 8601`)
}
const date = new Date(raw)
if (Number.isNaN(date.getTime())) {
return new Error(`${field} inválido. Use uma data no formato ISO 8601`)
}
return date
}

function parseOptionalNumber(raw: unknown, field: string): number | null | Error {
if (raw === undefined || raw === null) {
return null
}
if (typeof raw !== 'number' || Number.isNaN(raw)) {
return new Error(`${field} inválido`)
}
return raw
}

function parseOptionalString(raw: unknown, field: string): string | null | Error {
if (raw === undefined || raw === null) {
return null
}
if (typeof raw !== 'string') {
return new Error(`${field} inválido`)
}
return raw
}
51 changes: 51 additions & 0 deletions src/application/evento/BuscarEventoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BuscarEventoPorIdUseCase } from '@/domain/evento/BuscarEventoPorIdUseCase'
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 {
buscarEventoPorIdUseCase: BuscarEventoPorIdUseCase
}

export class BuscarEventoController implements RequestHandler {
private readonly buscarEventoPorIdUseCase: BuscarEventoPorIdUseCase

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

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

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

const result = await this.buscarEventoPorIdUseCase.execute({
id: Number(eventoId)
})

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

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

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