From f9fb4f1ab2f2ea6ff0cd4f8a08de068d0a8b3fcb Mon Sep 17 00:00:00 2001 From: Moran <105233020+feliperm17@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:36:01 -0300 Subject: [PATCH 1/4] =?UTF-8?q?Altera=C3=A7=C3=B5es=20no=20splinker-contro?= =?UTF-8?q?ller=20para=20melhorar=20o=20arquivo=20exportado=20(#358)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/splinker-controller.js | 75 ++++++++++++++------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/controllers/splinker-controller.js b/src/controllers/splinker-controller.js index 9e7d9ca..07e9e99 100644 --- a/src/controllers/splinker-controller.js +++ b/src/controllers/splinker-controller.js @@ -1,5 +1,7 @@ import { format } from 'date-fns'; +import { converteDecimalParaGrausMinutosSegundos } from '~/helpers/coordenadas'; + import models from '../models'; const { @@ -30,6 +32,7 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { const tombos = await Tombo.findAll({ limit, offset, + order: [['hcf', 'ASC']], attributes: [ 'data_coleta_mes', 'data_coleta_ano', @@ -134,15 +137,15 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { }); tombos.forEach(tombo => { - const kingdom = tombo.familia?.reino?.nome || ' '; - const family = tombo.familia?.nome || ' '; - const genus = tombo.genero?.nome || ' '; - const species = tombo.especy?.nome || ' '; - const scientificNameAuthor = tombo.especy?.autor?.nome || ' '; - const commonName = tombo.nomes_populares || ' '; - const catalogNumber = tombo.hcf || ' '; + const kingdom = tombo.familia?.reino?.nome || '\t'; + const family = tombo.familia?.nome || '\t'; + const genus = tombo.genero?.nome || '\t'; + const species = tombo.especy?.nome || '\t'; + const scientificNameAuthor = tombo.especy?.autor?.nome || '\t'; + const commonName = tombo.nomes_populares || '\t'; + const catalogNumber = tombo.hcf || '\t'; const basisOfRecord = 'PreservedSpecimen'; - const typeStatus = tombo.tipo ? tombo.tipo?.nome : ' '; + const typeStatus = tombo.tipo ? tombo.tipo?.nome : '\t'; const collectionDate = [ tombo.data_coleta_ano, tombo.data_coleta_mes?.toString().padStart(2, '0'), @@ -150,15 +153,19 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { ] .filter(Boolean) .join('-'); - const collectorName = tombo.coletore?.nome || ' '; - const collectorNumber = tombo.numero_coleta || ' '; - const country = tombo.locais_coletum?.cidade?.estado?.paise?.nome || ' '; - const stateOrProvince = tombo.locais_coletum?.cidade?.estado?.sigla || ' '; - const city = tombo.locais_coletum?.cidade?.nome || ' '; - const locality = tombo.locais_coletum?.descricao || ' '; - const latitude = tombo.latitude || ' '; - const longitude = tombo.longitude || ' '; - const elevation = tombo.altitude || ' '; + const collectorName = tombo.coletore?.nome || '\t'; + const collectorNumber = tombo.numero_coleta || '\t'; + const country = tombo.locais_coletum?.cidade?.estado?.paise?.nome || '\t'; + const stateOrProvince = tombo.locais_coletum?.cidade?.estado?.sigla || '\t'; + const city = tombo.locais_coletum?.cidade?.nome || '\t'; + const locality = tombo.locais_coletum?.descricao || '\t'; + const latitude = tombo.latitude + ? converteDecimalParaGrausMinutosSegundos(tombo.latitude, false, true) + : '\t'; + const longitude = tombo.longitude + ? converteDecimalParaGrausMinutosSegundos(tombo.longitude, true, true) + : '\t'; + const elevation = tombo.altitude + ' m' || '\t'; const identificationDate = [ tombo.data_identificacao_ano, tombo.data_identificacao_mes?.toString().padStart(2, '0'), @@ -168,35 +175,35 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { .join('-'); const identifierName = tombo.identificadores ? tombo.identificadores.map(i => i.nome).join(', ') - : ' '; + : '\t'; const notes = tombo.tombos_fotos?.length > 0 - ? `${tombo.tombos_fotos.map(foto => `[BARCODE=${foto.codigo_barra}]`).join(' , ')} ${tombo.observacao || ' '}` - : tombo.observacao || ' '; + ? `${tombo.tombos_fotos.map(foto => `[BARCODE=${foto.codigo_barra}]`).join(' , ')} ${tombo.observacao || '\t'}` + : tombo.observacao || '\t'; const linha = [ kingdom, - ' ', // Phylum - ' ', // Class - ' ', // Order + '\t', // Phylum + '\t', // Class + '\t', // Order family, genus, species, - ' ', // Subspecies + '\t', // Subspecies scientificNameAuthor, commonName, - ' ', // Field number + '\t', // Field number catalogNumber, - ' ', // Previous catalog number + '\t', // Previous catalog number basisOfRecord, typeStatus, - ' ', // Preparation type - ' ', // Individual count - ' ', // Specimen sex - ' ', // Specimen life stage + '\t', // Preparation type + '\t', // Individual count + '\t', // Specimen sex + '\t', // Specimen life stage collectionDate, collectorName, collectorNumber, - ' ', // Continent or Ocean + '\t', // Continent or Ocean country, stateOrProvince, city, @@ -204,11 +211,11 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { latitude, longitude, elevation, - ' ', // Depth + '\t', // Depth identificationDate, identifierName, - ' ', // Related catalog item - ' ', // Relationship type + '\t', // Related catalog item + '\t', // Relationship type notes, ].join('|'); From b9febdc822a3311ef1c594d0ea44ad9438831f9b Mon Sep 17 00:00:00 2001 From: Moran <105233020+feliperm17@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:05:38 -0300 Subject: [PATCH 2/4] Remove Coluna 'complemento' da tabela 'locais_coleta' (#359) --- .../20260224225625_remove_complemento_locais_coleta.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/database/migration/20260224225625_remove_complemento_locais_coleta.ts diff --git a/src/database/migration/20260224225625_remove_complemento_locais_coleta.ts b/src/database/migration/20260224225625_remove_complemento_locais_coleta.ts new file mode 100644 index 0000000..116f56b --- /dev/null +++ b/src/database/migration/20260224225625_remove_complemento_locais_coleta.ts @@ -0,0 +1,8 @@ +import { Knex } from 'knex' + +export async function run(knex: Knex): Promise { + await knex.raw(` + ALTER TABLE locais_coleta + DROP COLUMN IF EXISTS complemento + `) +} From 37b822abe071efb0034facbebc320fa1428573ac Mon Sep 17 00:00:00 2001 From: Moran <105233020+feliperm17@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:12:48 -0300 Subject: [PATCH 3/4] 297 sanitiza local coleta (#349) --- ...218133831_sanitiza_locais_coleta_vazios.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/database/migration/20260218133831_sanitiza_locais_coleta_vazios.ts diff --git a/src/database/migration/20260218133831_sanitiza_locais_coleta_vazios.ts b/src/database/migration/20260218133831_sanitiza_locais_coleta_vazios.ts new file mode 100644 index 0000000..0027f22 --- /dev/null +++ b/src/database/migration/20260218133831_sanitiza_locais_coleta_vazios.ts @@ -0,0 +1,21 @@ +import { Knex } from 'knex' + +export async function run(knex: Knex): Promise { + await knex.transaction(async trx => { + await trx.raw(` + UPDATE tombos + SET local_coleta_id = NULL + FROM locais_coleta lc + WHERE tombos.local_coleta_id = lc.id + AND (lc.descricao IS NULL OR TRIM(lc.descricao) = '') + `) + + await trx.raw(` + DELETE FROM locais_coleta lc + WHERE (lc.descricao IS NULL OR TRIM(lc.descricao) = '') + AND NOT EXISTS ( + SELECT 1 FROM tombos t WHERE t.local_coleta_id = lc.id + ) + `) + }) +} From a7629202a2cec6486ea962491cb183aafd565305 Mon Sep 17 00:00:00 2001 From: Edvaldo Szymonek Date: Wed, 25 Feb 2026 23:08:24 -0300 Subject: [PATCH 4/4] =?UTF-8?q?migra=20script=20de=20sincroniza=C3=A7?= =?UTF-8?q?=C3=A3o=20para=20o=20postgres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/cspell.yml | 1 + script/database-sync/Dockerfile | 22 ++++++++++++++++--- script/database-sync/database-sync.sh | 28 +++++++++++++------------ script/database-sync/docker-compose.yml | 16 +++++++------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/.config/cspell.yml b/.config/cspell.yml index f7fae0c..7004104 100644 --- a/.config/cspell.yml +++ b/.config/cspell.yml @@ -1,6 +1,7 @@ language: en,pt-BR words: - postgresql + - postgres - herbario - distro - caprover diff --git a/script/database-sync/Dockerfile b/script/database-sync/Dockerfile index 9e9e19d..d38515f 100644 --- a/script/database-sync/Dockerfile +++ b/script/database-sync/Dockerfile @@ -1,10 +1,26 @@ FROM debian:12-slim -RUN apt-get update && \ +RUN \ + apt-get update && \ apt-get install -y \ - default-mysql-client \ cron \ - tzdata && \ + tzdata \ + gnupg \ + curl \ + ca-certificates && \ + + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ + + apt-get update && \ + apt-get install -y \ + postgresql-client-18 && \ + + apt-get remove -y \ + curl \ + ca-certificates \ + gnupg && \ + rm -rf /var/lib/apt/lists/* ENV TZ=America/Sao_Paulo diff --git a/script/database-sync/database-sync.sh b/script/database-sync/database-sync.sh index 3b271f2..866b6bf 100755 --- a/script/database-sync/database-sync.sh +++ b/script/database-sync/database-sync.sh @@ -7,9 +7,9 @@ EXCLUDE_PARAMS="" if [ -n "$SYNC_EXCLUDE_TABLES" ]; then IFS=',' read -ra TABLES <<< "$SYNC_EXCLUDE_TABLES" for table in "${TABLES[@]}"; do - table=$(echo "$table" | xargs) # trim whitespace + table=$(echo "$table" | xargs) if [ -n "$table" ]; then - EXCLUDE_PARAMS="$EXCLUDE_PARAMS --ignore-table=${SYNC_SOURCE_DATABASE}.${table}" + EXCLUDE_PARAMS="$EXCLUDE_PARAMS --exclude-table=$table" fi done echo "Excluding tables: $SYNC_EXCLUDE_TABLES" @@ -17,20 +17,22 @@ fi echo "Starting synchronization process..." -mysqldump \ +PGPASSWORD="$SYNC_SOURCE_PASSWORD" pg_dump \ -h "$SYNC_SOURCE_HOST" \ - -P "$SYNC_SOURCE_PORT" \ - -u "$SYNC_SOURCE_USER" \ - ${SYNC_SOURCE_PASSWORD:+-p"$SYNC_SOURCE_PASSWORD"} \ + -p "$SYNC_SOURCE_PORT" \ + -U "$SYNC_SOURCE_USER" \ + --clean \ + --if-exists \ + --no-owner \ + --no-acl \ + --format plain \ $EXCLUDE_PARAMS \ - --single-transaction \ "$SYNC_SOURCE_DATABASE" | \ -mysql \ +PGPASSWORD="$SYNC_DEST_PASSWORD" psql \ -h "$SYNC_DEST_HOST" \ - -P "$SYNC_DEST_PORT" \ - -u "$SYNC_DEST_USER" \ - ${SYNC_DEST_PASSWORD:+-p"$SYNC_DEST_PASSWORD"} \ - "$SYNC_DEST_DATABASE" - + -p "$SYNC_DEST_PORT" \ + -U "$SYNC_DEST_USER" \ + -d "$SYNC_DEST_DATABASE" \ + --single-transaction echo "Synchronization completed successfully: ${SYNC_SOURCE_DATABASE} -> ${SYNC_DEST_DATABASE}" diff --git a/script/database-sync/docker-compose.yml b/script/database-sync/docker-compose.yml index 26ccf78..2aa8e08 100644 --- a/script/database-sync/docker-compose.yml +++ b/script/database-sync/docker-compose.yml @@ -5,15 +5,15 @@ services: build: . environment: SYNC_SOURCE_HOST: "127.0.0.1" - SYNC_SOURCE_PORT: "3306" - SYNC_SOURCE_USER: "root" + SYNC_SOURCE_PORT: "5432" + SYNC_SOURCE_USER: "postgres" SYNC_SOURCE_PASSWORD: "masterkey" - SYNC_SOURCE_DATABASE: "herbario_dev" + SYNC_SOURCE_DATABASE: "herbario_prod" SYNC_DEST_HOST: "127.0.0.1" - SYNC_DEST_PORT: "3306" - SYNC_DEST_USER: "root" + SYNC_DEST_PORT: "5432" + SYNC_DEST_USER: "postgres" SYNC_DEST_PASSWORD: "masterkey" - SYNC_DEST_DATABASE: "herbario_test" - SYNC_EXCLUDE_TABLES: "usuarios" - CRON_SCHEDULE: "0 3 * * *" + SYNC_DEST_DATABASE: "herbario_dev" + SYNC_EXCLUDE_TABLES: "" + CRON_SCHEDULE: "*/2 * * * *" TZ: "America/Sao_Paulo"