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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ TZ=UTC
PORT=3000
NODE_ENV=development

# Optional: CORS (used by Application). Comma-separated origins, or * for all.
CORS_ORIGINS=*
CORS_METHODS=HEAD,GET,POST,PUT,PATCH,DELETE
CORS_ALLOWED_HEADERS=Content-Type,Authorization

STORAGE_PATH=./uploads

MYSQL_DATABASE=herbario_dev
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
POSTGRES_USER: $PG_USERNAME
POSTGRES_PASSWORD: $PG_PASSWORD
volumes:
- postgres_data:/var/lib/postgresql/data
- postgres_data:/var/lib/postgresql
ports:
- ${PG_PORT:-5432}:5432
shm_size: 128mb
Expand Down
16 changes: 16 additions & 0 deletions eslint.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ export default defineConfig([
'@stylistic/object-property-newline': [
'error',
{ allowAllPropertiesOnSameLine: true }
],
'@stylistic/operator-linebreak': [
'warn',
'before',
{ overrides: { '=': 'after' } }
],
'@stylistic/quote-props': [
'error',
'as-needed'
],
'no-restricted-syntax': [
'warn',
{
message: 'Use EnumOf pattern instead of TypeScript enums.',
selector: 'TSEnumDeclaration'
}
]
}
},
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"lint": "run-p tsc:check lint:eslint",
"clean": "rimraf dist",
"build:app": "babel -d ./dist -x '.js,.ts,.tsx' --copy-files --no-copy-ignored ./src",
"start": "nodemon --ext 'js,ts,tsx' --exec babel-node --extensions '.js,.ts,.tsx' ./src/index.js",
"start": "nodemon --ext 'js,ts,tsx' --exec babel-node --extensions '.js,.ts,.tsx' ./src/application/index.ts",
"build": "run-s clean build:app",
"test": "vitest --run",
"test:coverage": "vitest --run --coverage",
Expand Down Expand Up @@ -70,10 +70,13 @@
"@babel/preset-typescript": "7.28.5",
"@eslint/js": "9.39.1",
"@stylistic/eslint-plugin": "5.5.0",
"@types/cors": "2.8.19",
"@types/express": "5.0.5",
"@types/morgan": "1.9.10",
"@types/pg": "^8.16.0",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/swagger-ui-express": "^4.1.8",
"@vitest/coverage-v8": "4.0.7",
"babel-plugin-module-resolver": "5.0.2",
"chai": "6.2.0",
Expand Down
102 changes: 0 additions & 102 deletions src/app.js

This file was deleted.

110 changes: 110 additions & 0 deletions src/application/Application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import makeCors from 'cors'
import express from 'express'
import makeHelmet from 'helmet'
import morgan from 'morgan'
// import swaggerUi from 'swagger-ui-express'

import { Method } from '@/library/http/common'
import { RequestHandler, Server } from '@/library/http/Server'

import { upload } from '../config/directory'
// import swaggerSpec from '../config/swagger'
import legacyErrors from '../middlewares/erros-middleware'
import { generatePreview, reportPreview } from '../reports/controller'

export interface Route {
method: Method
path: string
handlers: RequestHandler[]
}

interface CorsParameters {
origins: string[]
methods: string[]
allowedHeaders: string[]
}

interface Parameters {
server: Server
routes: Route[]
legacyRouter?: unknown
cors: CorsParameters
}

const securityConfig = {
crossOriginEmbedderPolicy: false,
contentSecurityPolicy: {
directives: {
defaultSrc: ['"self"'],
styleSrc: ['"self"', '"unsafe-inline"'],
scriptSrc: ['"self"'],
imgSrc: [
'"self"',
'"data:"',
'"https:"'
]
}
}
}

export class Application {
private readonly server: Server
private readonly routes: Route[]
private readonly legacyRouter?: unknown

constructor({
server, routes,
legacyRouter, cors
}: Parameters) {
this.server = server
this.routes = routes
this.legacyRouter = legacyRouter

this.setup({ cors })
}

private setup({ cors }: { cors: CorsParameters }): void {
this.server
.use(makeHelmet(securityConfig))
.use(makeCors({
origin: cors.origins,
methods: cors.methods,
allowedHeaders: cors.allowedHeaders
}))
.use(morgan('dev'))
// .use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
// .use('/fotos', express.static(upload))
// .use('/assets', express.static(assets))
.use(
'/uploads',
express.static(upload, {
index: false,
redirect: false,
setHeaders: res => {
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable')
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin')
}
})
)

const reportsRouter = express.Router()
reportsRouter.get('/:fileName', reportPreview)
reportsRouter.post('/:fileName', generatePreview)
this.server.use('/reports', reportsRouter)

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

if (this.legacyRouter) {
this.server.mount(this.legacyRouter)
}

this.server.use(legacyErrors)
}

async start(port: number): Promise<void> {
await this.server.start(port)
}
}
36 changes: 36 additions & 0 deletions src/application/estado/ListaEstadosController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ListaEstadosUseCase } from '@/domain/estado/ListaEstadosUseCase'
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 {
listaEstadosUseCase: ListaEstadosUseCase
}

export class ListaEstadosController implements RequestHandler {
private readonly listaEstadosUseCase: ListaEstadosUseCase

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

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

if (!paisSigla) {
return new BadRequestError({ message: 'paisSigla é obrigatório' })
}

const result = await this.listaEstadosUseCase.execute({ paisSigla })

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

return { body: result.value, statusCode: StatusCode.Ok }
}
}
20 changes: 20 additions & 0 deletions src/application/estado/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ListaEstadosUseCase } from '@/domain/estado/ListaEstadosUseCase'
import { createEstadoCollection } from '@/factory/EstadoCollectionFactory'
import { Method } from '@/library/http/common'

import { Route } from '../Application'
import { ListaEstadosController } from './ListaEstadosController'

const estadoCollection = createEstadoCollection()

export const routes: Route[] = [
{
handlers: [
new ListaEstadosController({
listaEstadosUseCase: new ListaEstadosUseCase({ estadoCollection })
})
],
method: Method.Get,
path: '/paises/:paisSigla/estados'
}
]
Loading
Loading