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
32 changes: 0 additions & 32 deletions .babelrc

This file was deleted.

20 changes: 9 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ FROM node:jod-slim AS build

WORKDIR /usr/src/app

COPY package.json yarn.lock tsconfig.json .babelrc ./
COPY package.json yarn.lock tsconfig.json build.mjs ./

RUN yarn install --production=false
RUN yarn install --frozen-lockfile --production=false

COPY ./src/ ./src/

Expand Down Expand Up @@ -56,20 +56,18 @@ RUN \
--shell /usr/sbin/nologin \
hcf_api

USER hcf_api

WORKDIR /home/hcf_api/app

COPY package.json yarn.lock ./
COPY --chown=hcf_api:hcf_api package.json yarn.lock ./

RUN yarn install --production && \
RUN yarn install --frozen-lockfile --production && \
yarn cache clean --force

COPY --from=build /usr/src/app/dist ./dist
COPY ./public ./public
COPY src/reports/assets/fonts/*.ttf /usr/share/fonts/truetype/

RUN chown -R hcf_api:hcf_api /home/hcf_api

USER hcf_api
COPY --from=build --chown=hcf_api:hcf_api /usr/src/app/dist ./dist
COPY --chown=hcf_api:hcf_api ./public ./public
COPY --from=build --chmod=664 /usr/src/app/dist/reports/assets/fonts/*.ttf /usr/share/fonts/truetype/

EXPOSE $PORT

Expand Down
26 changes: 26 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { rmSync } from 'node:fs'
import { build } from 'esbuild'
import { globSync } from 'glob'

rmSync('dist', { recursive: true, force: true })

const assets = globSync('src/**/*.{ejs,html,ttf,css}')

await build({
entryPoints: [
...assets,
'src/index.js',
],
outdir: 'dist',
bundle: true,
minify: true,
platform: 'node',
target: 'node20',
packages: 'external',
loader: {
'.ejs': 'copy',
'.html': 'copy',
'.ttf': 'copy',
'.css': 'copy',
}
})
8 changes: 7 additions & 1 deletion docker-compose.yml → compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ services:
context: .
dockerfile: Dockerfile
platform: linux/amd64
image: hcf-api:local
image: herbario-api:local
container_name: herbario_api
env_file: .env
environment:
PG_HOST: postgres
ports:
- $PORT:$PORT
networks:
- herbario_network
depends_on:
- postgres

nginx:
image: nginx:1.17-alpine
Expand All @@ -43,3 +47,5 @@ services:
- ./uploads:/usr/share/nginx/html
ports:
- 8080:80
depends_on:
- api
5 changes: 3 additions & 2 deletions eslint.config.mts → eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import js from '@eslint/js'
import stylistic from '@stylistic/eslint-plugin'
import type { ESLint } from 'eslint'
// @ts-expect-error: No declaration file found
import importHelpers from 'eslint-plugin-import-helpers'
import { defineConfig } from 'eslint/config'
Expand All @@ -15,6 +14,8 @@ export default defineConfig([
'public/',
'coverage/',
'node_modules/',
'build.mjs',
'eslint.config.mjs',
'.pnp.cjs',
'.pnp.loader.mjs',
'.yarn/',
Expand All @@ -27,7 +28,7 @@ export default defineConfig([
js,
stylistic,
tseslint,
'import-helpers': importHelpers as unknown as ESLint.Plugin
"import-helpers": importHelpers
},
languageOptions: {
parserOptions: {
Expand Down
21 changes: 5 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
"tsc:check": "tsc --noEmit",
"lint:eslint": "eslint --cache",
"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",
"build": "run-s clean build:app",
"start": "tsx --watch --env-file=.env src/index.js",
"build": "node build.mjs",
"test": "vitest --run",
"test:coverage": "vitest --run --coverage",
"prepare": "husky",
Expand Down Expand Up @@ -58,36 +56,27 @@
"swagger-jsdoc": "6.2.8",
"swagger-ui-express": "5.0.1",
"throttled-queue": "3.0.0",
"tsx": "4.20.6",
"uuid": "13.0.0",
"wkx": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "7.28.3",
"@babel/core": "7.28.5",
"@babel/node": "7.28.0",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/preset-env": "7.28.5",
"@babel/preset-react": "7.28.5",
"@babel/preset-typescript": "7.28.5",
"@eslint/js": "9.39.1",
"@stylistic/eslint-plugin": "5.5.0",
"@types/express": "5.0.5",
"@types/pg": "^8.16.0",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@vitest/coverage-v8": "4.0.7",
"babel-plugin-module-resolver": "5.0.2",
"chai": "6.2.0",
"chai-http": "^5.1.2",
"esbuild": "0.28.0",
"eslint": "9.39.1",
"eslint-plugin-import-helpers": "2.0.1",
"glob": "10.5.0",
"globals": "16.5.0",
"husky": "9.1.7",
"jiti": "2.6.1",
"nodemon": "3.1.10",
"npm-run-all": "4.1.5",
"rimraf": "6.1.2",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.46.3",
"vitest": "4.0.7"
Expand Down
16 changes: 11 additions & 5 deletions script/database-backup/backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ echo "Iniciando processo de backup: ${FILE_NAME}"

echo "Dumping e comprimindo database..."
PGPASSWORD="${DATABASE_PASSWORD}" pg_dump \
-h "${DATABASE_HOST}" \
-p "${DATABASE_PORT}" \
-U "${DATABASE_USER}" \
-d "${DATABASE_NAME}" \
-Fp | gzip > "${TMP_FILE}"
--host "${DATABASE_HOST}" \
--port "${DATABASE_PORT}" \
--username "${DATABASE_USER}" \
--dbname "${DATABASE_NAME}" \
--clean \
--if-exists \
--format plain \
--encoding UTF-8 \
--no-privileges \
--no-owner \
--verbose | gzip > "${TMP_FILE}"

echo "Enviando para o Google Drive..."
rclone copy "${TMP_FILE}" gdrive:
Expand Down
22 changes: 18 additions & 4 deletions src/config/directory.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';

const { UPLOAD_PATH, STORAGE_PATH } = process.env;
const { UPLOAD_PATH, STORAGE_PATH, SOURCE_ROOT } = process.env;

export const assets = resolve(__dirname, '../../public');
export const upload = UPLOAD_PATH || resolve(__dirname, '../../uploads');
export const storage = STORAGE_PATH || resolve(__dirname, '../../storage');
function getSourceRoot() {
if (SOURCE_ROOT) {
return SOURCE_ROOT;
}

if (!existsSync(resolve('src/views')) && existsSync(resolve('dist/views'))) {
return 'dist';
}

return 'src';
}

export const resolveSource = (...parts) => resolve(getSourceRoot(), ...parts);
export const assets = resolve('public');
export const upload = UPLOAD_PATH || resolve('uploads');
export const storage = STORAGE_PATH || resolve('storage');

export default {};
6 changes: 2 additions & 4 deletions src/controllers/fichas-tombos-controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import moment from 'moment-timezone';
import path from 'path';

import { converteDecimalParaGrausMinutosSegundos } from '~/helpers/coordenadas';

// import identificador from '~/routes/identificador';

import { resolveSource } from '../config/directory';
import formataColunasSeparadas from '../helpers/formata-colunas-separadas';
import renderizaArquivoHtml from '../helpers/renderiza-arquivo-html';
import models from '../models';
Expand Down Expand Up @@ -342,7 +340,7 @@ export default function fichaTomboController(request, response, next) {
codigo_barras_selecionado: code,
};

const caminhoArquivoHtml = path.resolve(__dirname, '../views/ficha-tombo.ejs');
const caminhoArquivoHtml = resolveSource('views/ficha-tombo.ejs');
return renderizaArquivoHtml(caminhoArquivoHtml, parametros)
.then(html => {
response.status(200).send(html);
Expand Down
35 changes: 26 additions & 9 deletions src/controllers/rfids-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ const getColetorPrincipal = tombo => (
tombo?.coletor?.nome || tombo?.Coletor?.nome || 'N/A'
);

const normalizarTid = tid => (typeof tid === 'string' ? tid.trim() : '');

const tidValido = tid => {
const tidNormalizado = normalizarTid(tid);
return tidNormalizado.length > 0 && tidNormalizado.toUpperCase() !== 'N/A';
};

const codificarParaEpcHex = (tomboHcf, codigoBarra) => {
const tomboId = parseInt(tomboHcf, 10);
const match = codigoBarra.match(/([a-zA-Z]+)(\d+)/);
Expand Down Expand Up @@ -145,16 +152,23 @@ export const finalizarGravacao = async (request, response, next) => {
break;

case 'CONCLUIDO':
if (tid) {
const tagExistente = await Rfid.findOne({ where: { tid } });
if (!tidValido(tid)) {
return response.status(400).json({
erro: 'TID obrigatório para concluir a gravação RFID.',
});
}

{
const tidNormalizado = normalizarTid(tid);
const tagExistente = await Rfid.findOne({ where: { tid: tidNormalizado } });

if (tagExistente && tagExistente.id !== Number(id)) {
return response.status(409).json({
erro: 'TID já se encontra vinculado a outro tombo no sistema.',
});
}

rfid.tid = tid;
rfid.tid = tidNormalizado;
}
break;

Expand Down Expand Up @@ -204,7 +218,8 @@ export const listagem = async (request, response, next) => {
whereFoto.codigo_barra = codigo_barra;
}

const rfids = await Rfid.findAndCountAll({
const rfids = await Rfid.unscoped().findAndCountAll({
attributes: ['id', 'tombo_foto_id', 'epc', 'tid', 'status', 'created_at', 'updated_at'],
limit: limite,
offset,
where: whereRfid,
Expand Down Expand Up @@ -311,12 +326,13 @@ export const listarPendentesRfid = async (request, response, next) => {
}
};

export const validarEpc = async (request, response, next) => {
const { epc } = request.params;
export const validarTid = async (request, response, next) => {
const { tid } = request.params;
const tidNormalizado = normalizarTid(tid);

try {
const rfid = await Rfid.findOne({
where: { epc: epc },
where: { tid: tidNormalizado },
include: [
{
model: TomboFoto,
Expand Down Expand Up @@ -344,7 +360,7 @@ export const validarEpc = async (request, response, next) => {
if (!rfid) {
return response.status(404).json({
valido: false,
mensagem: 'EPC não encontrado.',
mensagem: 'TID não encontrado.',
});
}

Expand All @@ -354,9 +370,10 @@ export const validarEpc = async (request, response, next) => {

return response.status(200).json({
valido: true,
mensagem: 'EPC validado com sucesso.',
mensagem: 'TID validado com sucesso.',
dados: {
id_rfid: rfidJson.id,
tid: rfidJson.tid,
epc_formatado: rfidJson.epc,
epc: decodificarEpcHex(rfidJson.epc),
status_rfid: rfidJson.status,
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/tombos-controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ForeignKeyConstraintError } from 'sequelize';
import { ForeignKeyConstraintError, fn, col } from 'sequelize';

import { padronizarNomeDarwincore } from '~/helpers/padroniza-nome-darwincore';

Expand All @@ -21,7 +21,7 @@ const SRID = {
const {
Solo, Relevo, Cidade, Estado, Vegetacao, FaseSucessional, Pais, Tipo, LocalColeta, Familia, sequelize,
Genero, Subfamilia, Autor, Coletor, Variedade, Subespecie, TomboFoto, Identificador,
ColecaoAnexa, Especie, Herbario, Tombo, Alteracao, TomboIdentificador, ColetorComplementar, Sequelize: { Op, fn, col },
ColecaoAnexa, Especie, Herbario, Tombo, Alteracao, TomboIdentificador, ColetorComplementar, Sequelize: { Op },
} = models;

function parseDataTombo(valor) {
Expand Down
5 changes: 2 additions & 3 deletions src/controllers/tombos-exportacoes-controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from 'path';

import { resolveSource } from '../config/directory';
import BadRequestException from '../errors/bad-request-exception';
import PreconditionFailedException from '../errors/precondition-failed-exception';
import knex from '../factories/knex';
Expand Down Expand Up @@ -189,7 +188,7 @@ export default function exportacoes(request, response, next) {
.sort(a => a == 'codigo_barra' ? 1 : -1);
const parametros = { colunas, tombos };

const caminhoArquivoHtml = path.resolve(__dirname, '../views/exportacao-tombos.ejs');
const caminhoArquivoHtml = resolveSource('views/exportacao-tombos.ejs');
return renderizaArquivoHtml(caminhoArquivoHtml, parametros)
.then(html => {
response.status(200).send(html);
Expand Down
Loading
Loading