O SnapOtter usa PostgreSQL 17 com [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) para persistência de dados. O esquema é definido em `apps/api/src/db/schema.ts`.
A conexão é configurada através da variável de ambiente `DATABASE_URL` (padrão `postgres://snapotter:snapotter@postgres:5432/snapotter`). No Docker Compose, o contêiner do Postgres armazena seus dados no volume nomeado `SnapOtter-pgdata`.
## Tabelas {#tables}
### users {#users}
Armazena contas de usuário. Criada automaticamente na primeira execução a partir de `DEFAULT_USERNAME` e `DEFAULT_PASSWORD`.
| Coluna | Tipo | Observações |
|---|---|---|
| `id` | uuid | Chave primária |
| `username` | varchar | Único, obrigatório |
| `passwordHash` | varchar | hash scrypt |
| `role` | varchar | `admin`, `editor` ou `user` |
| `mustChangePassword` | boolean | Flag de redefinição de senha forçada |
| `createdAt` | timestamp | Horário de criação |
| `updatedAt` | timestamp | Horário da última atualização |
### sessions {#sessions}
Sessões de login ativas. Cada linha vincula um token de sessão a um usuário.
Biblioteca de arquivos persistente. Uma edição salva é inserida por padrão como uma linha raiz independente ("salvar como novo": `version` 1, `parentId` null, então o original permanece listado), ou como uma versão vinculada ao pai quando você sobrescreve o original (`parentId` definido, `version` incrementado, substituindo-o). A coluna `toolChain` registra as ferramentas aplicadas.
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
```
### Migrando da versão 1.x (SQLite) {#migrating-from-1-x-sqlite}
Atualizar do SnapOtter 1.x tem seu próprio guia: veja [Atualizando da 1.x para a 2.0](./upgrading). Em resumo, reutilize seu volume `/data` existente e a 2.0 detecta e importa automaticamente o `/data/snapotter.db` na primeira inicialização (ou defina `SQLITE_MIGRATE_PATH` para apontar para ele explicitamente). Faça backup de todo o volume `/data` primeiro, não apenas de `snapotter.db`: a 1.x usa o modo WAL do SQLite, então um contêiner parado frequentemente deixa a maior parte de seus dados em `snapotter.db-wal` ao lado de um `snapotter.db` quase vazio.