From e23df98b52f799edaa59a20378ffe16eedf6409b Mon Sep 17 00:00:00 2001 From: Edvaldo Szymonek Date: Wed, 16 Sep 2026 11:48:15 -0300 Subject: [PATCH 1/2] Atualiza README --- .env.example | 9 ----- README.md | 106 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 35 deletions(-) diff --git a/.env.example b/.env.example index 94a12608..dbbd176a 100644 --- a/.env.example +++ b/.env.example @@ -9,15 +9,6 @@ CORS_ALLOWED_HEADERS=Content-Type,Authorization STORAGE_PATH=./uploads -MYSQL_DATABASE=herbario_dev -MYSQL_HOST=127.0.0.1 -MYSQL_PORT=3306 -MYSQL_USERNAME=root -MYSQL_PASSWORD=masterkey - -MYSQL_MIGRATION_USERNAME=root -MYSQL_MIGRATION_PASSWORD=masterkey - RECAPTCHA_SECRET_KEY=6LcYYYYYYYYYYYYYY JWT_SECRET=your-jwt-secret-here diff --git a/README.md b/README.md index a67e7c08..dcf9bf68 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,108 @@ # HCF API +HTTP API do sistema do Herbário da UTFPR (HCF). O painel web usa, em desenvolvimento, `PAINEL_BASE_URL=http://localhost:5173`. + ## Pré-requisitos -- [NodeJS >= 22](https://nodejs.org/en) -- [Yarn](https://yarnpkg.com) -- [Docker](https://www.docker.com) -- [Docker Compose](https://docs.docker.com/compose) +- [Node.js 22](https://nodejs.org/) (`.nvmrc` aponta para `lts/jod`; com nvm: `nvm use`) +- [Yarn](https://yarnpkg.com/) (Classic; o repositório usa `yarn.lock`) +- [Docker](https://docs.docker.com/get-docker/) com Compose V2 (`docker compose`) +- Git +- Um cliente para restaurar o dump: [DBeaver](https://dbeaver.io/) ou `psql` + +## Variáveis de ambiente + +Clone o repositório e copie o arquivo de ambiente: + +```shell +cp .env.example .env +``` -## Configuração +Os nomes e os valores padrão locais estão em `.env.example`. Ajuste o `.env` se precisar. Não commite segredos. -Após a instalação dos programas necessários, faça uma cópia do arquivo `.env.example` e renomeie como `.env`. +| Grupo | Variáveis | Uso local | +| --- | --- | --- | +| Runtime | `TZ`, `PORT`, `NODE_ENV`, `STORAGE_PATH` | Os padrões do exemplo bastam. | +| CORS | `CORS_ORIGINS`, `CORS_METHODS`, `CORS_ALLOWED_HEADERS` | `*` ou a origem do painel. | +| Postgres | `PG_DATABASE`, `PG_HOST`, `PG_PORT`, `PG_USERNAME`, `PG_PASSWORD`, `PG_MIGRATION_USERNAME`, `PG_MIGRATION_PASSWORD` | O Compose usa `PG_DATABASE`, `PG_USERNAME`, `PG_PASSWORD` e `PG_PORT`. A API usa `PG_*`. | +| Auth, e-mail, captcha | `JWT_SECRET`, `SMTP_*`, `RECAPTCHA_SECRET_KEY` | Login, troca de senha e reCAPTCHA. | +| Painel | `PAINEL_BASE_URL` | Padrão local: `http://localhost:5173`. | ## Banco de dados -Para subir o banco de dados do projeto, execute no terminal: +Peça um dump a um colega de time (não há dump neste repositório). Suba o PostgreSQL: ```shell -$ docker-compose up mysql +docker compose up postgres ``` -Os dados de conexão do banco de dados estão dentro do arquivo `.env` criado anteriormente. +Conecte com os valores de `PG_*` do `.env` (padrões de `.env.example`: host `127.0.0.1`, porta `5432`, usuário `postgres`, senha `masterkey`, banco `herbario_dev`). -## Execução +Pode usar um cliente gráfico como o DBeaver (importe ou execute o dump na conexão) ou o terminal com `psql`. + +Dump compactado (`.sql.gz`): + +```shell +gunzip -c caminho/para/dump.sql.gz | \ + PGPASSWORD=masterkey psql \ + -h 127.0.0.1 \ + -p 5432 \ + -U postgres \ + -d herbario_dev +``` -No terminal, digite os comandos abaixo: +Dump em SQL puro: ```shell -$ yarn install -$ yarn start +PGPASSWORD=masterkey psql \ + -h 127.0.0.1 \ + -p 5432 \ + -U postgres \ + -d herbario_dev \ + -f caminho/para/dump.sql ``` -Você deve ver uma saída semelhante a demonstração abaixo. +## Execução + +```shell +yarn install +yarn start +``` + +`yarn start` recarrega ao alterar arquivos (`tsx --watch --env-file=.env`). Saída esperada em desenvolvimento: ```txt Using "development" environment Master 18385 is running -Worker 18385 started on port 3000 +Server is running on port 3000 ``` ---- +Confira com `GET http://localhost:3000/health` (`{ "status": "OK" }`). + +## Scripts + +| Comando | Função | +| --- | --- | +| `yarn start` | Servidor de desenvolvimento com watch | +| `yarn build` | Bundle de produção (`dist/`) | +| `yarn lint` | `tsc --noEmit` e ESLint | +| `yarn test` | Todos os projetos Vitest | +| `yarn test:unit` / `yarn test:unit:watch` | Testes unitários | +| `yarn test:integration` / `yarn test:integration:watch` | Testes de integração (Postgres de teste) | +| `yarn test:coverage` | Cobertura dos testes unitários | +| `yarn migration:create` / `yarn migration:apply` | Autores de mudança de schema | -- adicionar busca pelo código de barras da foto -- colocar um alerta no painel quando estiver em ambiente de desenvolvimento -- issue para corrigir o DarwinCore (problema com o botão, quando clica não está funcionando) -- issue para o incremento do hcf na api ao invés do painel -- relatorio de quantidade de tombos por periodo - - relatorio tem que ter um grafico, mostrando quantos tombos foram cadastrados por periodo, somente quantitativo +O `yarn install` configura o Husky. O hook `pre-push` roda os testes unitários. -- alteração na ficha tombo +## Testes + +**Unitários:** `yarn test:unit` — não precisa de Docker. + +**Integração:** sobe um PostgreSQL separado (porta **5433**, banco `herbario_test`, credenciais iguais às de `.env.test`). O schema vem de `test/integration/setup/schema.sql` na inicialização do container. + +```shell +docker compose -f compose.integration.yml up -d +yarn test:integration +``` -nome cientifico = genero, especie, subespecie (se tiver), variedade (se tiver) -um unico tombo tem variedade e subespecie. como devemos exibir no nome cientifico? -inverter a ordem, colocar subespecie e depois variedade +Mais detalhes em `test/integration/README.md`. From 366443b6e8067463f4031d40fcbba3ceb2409242 Mon Sep 17 00:00:00 2001 From: Edvaldo Szymonek Date: Sat, 26 Sep 2026 22:12:59 -0300 Subject: [PATCH 2/2] =?UTF-8?q?cria=20playbook=20para=20cria=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20usu=C3=A1rio=20do=20postgresql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ansible/ansible.cfg | 6 + ansible/group_vars/.gitkeep | 0 ansible/playbooks/04_api_database_access.yml | 127 +++++++++++++++++++ 3 files changed, 133 insertions(+) delete mode 100644 ansible/group_vars/.gitkeep create mode 100644 ansible/playbooks/04_api_database_access.yml diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index f5f492cf..a5031dd5 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -2,3 +2,9 @@ inventory = inventory roles_path = roles host_key_checking = false +retry_files_enabled = false +interpreter_python = auto_silent +vault_password_file = inventory/.vault_pass + +[ssh_connection] +pipelining = true diff --git a/ansible/group_vars/.gitkeep b/ansible/group_vars/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/ansible/playbooks/04_api_database_access.yml b/ansible/playbooks/04_api_database_access.yml new file mode 100644 index 00000000..5bfa0bfd --- /dev/null +++ b/ansible/playbooks/04_api_database_access.yml @@ -0,0 +1,127 @@ +- name: Setup API database access + hosts: herbario + become: true + + roles: + - postgresql_client + + tasks: + - name: Ensure API PostgreSQL user is present + tags: + - postgres + community.postgresql.postgresql_user: + name: "{{ item.name }}" + password: "{{ item.password }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + state: present + loop: "{{ postgres.api_users }}" + loop_control: + label: "{{ item.name }}" + + - name: Grant CONNECT on the API database + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.database }}" + type: database + privs: CONNECT + roles: "{{ item.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users }}" + loop_control: + label: "{{ item.name }} -> {{ item.database }}" + + - name: Grant USAGE on schemas + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.0.database }}" + type: schema + objs: "{{ item.1 }}" + privs: USAGE + roles: "{{ item.0.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users | product(['public', 'topology']) | list }}" + loop_control: + label: "{{ item.0.name }} {{ item.0.database }}.{{ item.1 }}" + + - name: Grant DML on all tables + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.0.database }}" + schema: "{{ item.1 }}" + type: table + objs: ALL_IN_SCHEMA + privs: SELECT,INSERT,UPDATE,DELETE + roles: "{{ item.0.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users | product(['public', 'topology']) | list }}" + loop_control: + label: "{{ item.0.name }} {{ item.0.database }}.{{ item.1 }}" + + - name: Grant USAGE,SELECT on all sequences + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.0.database }}" + schema: "{{ item.1 }}" + type: sequence + objs: ALL_IN_SCHEMA + privs: USAGE,SELECT + roles: "{{ item.0.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users | product(['public', 'topology']) | list }}" + loop_control: + label: "{{ item.0.name }} {{ item.0.database }}.{{ item.1 }}" + + - name: Set default privileges for future tables + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.0.database }}" + schema: "{{ item.1 }}" + type: default_privs + objs: tables + privs: SELECT,INSERT,UPDATE,DELETE + roles: "{{ item.0.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users | product(['public', 'topology']) | list }}" + loop_control: + label: "{{ item.0.name }} {{ item.0.database }}.{{ item.1 }}" + + - name: Set default privileges for future sequences + tags: + - postgres + community.postgresql.postgresql_privs: + database: "{{ item.0.database }}" + schema: "{{ item.1 }}" + type: default_privs + objs: sequences + privs: USAGE,SELECT + roles: "{{ item.0.name }}" + login_user: "{{ postgres.login_user }}" + login_host: "{{ postgres.login_host }}" + login_port: "{{ postgres.login_port }}" + login_password: "{{ postgres.login_password }}" + loop: "{{ postgres.api_users | product(['public', 'topology']) | list }}" + loop_control: + label: "{{ item.0.name }} {{ item.0.database }}.{{ item.1 }}"