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
9 changes: 0 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 80 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`.
6 changes: 6 additions & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Empty file removed ansible/group_vars/.gitkeep
Empty file.
127 changes: 127 additions & 0 deletions ansible/playbooks/04_api_database_access.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
Loading