SnapOtter menggunakan PostgreSQL 17 dengan [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) untuk persistensi data. Skema didefinisikan di `apps/api/src/db/schema.ts`.
Koneksi dikonfigurasi melalui variabel lingkungan `DATABASE_URL` (default `postgres://snapotter:snapotter@postgres:5432/snapotter`). Di Docker Compose, kontainer Postgres menyimpan datanya di volume bernama `SnapOtter-pgdata`.
## Tabel {#tables}
### users {#users}
Menyimpan akun pengguna. Dibuat otomatis pada saat pertama kali dijalankan dari `DEFAULT_USERNAME` dan `DEFAULT_PASSWORD`.
| Kolom | Tipe | Catatan |
|---|---|---|
| `id` | uuid | Primary key |
| `username` | varchar | Unik, wajib |
| `passwordHash` | varchar | hash scrypt |
| `role` | varchar | `admin`, `editor`, atau `user` |
| `mustChangePassword` | boolean | Flag reset kata sandi paksa |
| `createdAt` | timestamp | Waktu pembuatan |
| `updatedAt` | timestamp | Waktu pembaruan terakhir |
### sessions {#sessions}
Sesi login aktif. Setiap baris mengaitkan token sesi ke seorang pengguna.
| Kolom | Tipe | Catatan |
|---|---|---|
| `id` | varchar | Primary key (token sesi) |
| `userId` | uuid | Foreign key ke `users.id` |
| `expiresAt` | timestamp | Waktu kedaluwarsa |
| `createdAt` | timestamp | Waktu pembuatan |
### teams {#teams}
Grup untuk mengorganisasi pengguna. Admin dapat menetapkan pengguna ke tim.
| Kolom | Tipe | Deskripsi |
|--------|------|-------------|
| `id` | uuid | Primary key |
| `name` | varchar (unik, maks 50 karakter) | Nama tim |
| `createdAt` | timestamp | Waktu pembuatan |
### api_keys {#api-keys}
API key untuk akses secara programatik. Kunci mentah ditampilkan sekali saat pembuatan; hanya hash yang disimpan.
| Kolom | Tipe | Catatan |
|---|---|---|
| `id` | uuid | Primary key |
| `userId` | uuid | Foreign key ke `users.id` |
| `keyHash` | varchar | hash scrypt dari kunci |
| `name` | varchar | Label yang diberikan pengguna |
| `createdAt` | timestamp | Waktu pembuatan |
| `lastUsedAt` | timestamp | Diperbarui pada setiap permintaan terautentikasi |
Kunci diberi awalan `si_` diikuti oleh 96 karakter heksadesimal (48 byte acak).
### pipelines {#pipelines}
Rangkaian tool tersimpan yang dibuat pengguna di UI.
Pustaka file persisten. Secara default, sebuah editan yang disimpan dimasukkan sebagai baris akar independen ("simpan sebagai baru": `version` 1, `parentId` null, sehingga file asli tetap terdaftar), atau sebagai versi yang tertaut ke induk ketika Anda menimpa file asli (`parentId` diisi, `version` dinaikkan, menggantikannya). Kolom `toolChain` mencatat tool yang diterapkan.
Status UI per pengguna, dikunci berdasarkan nama preferensi. Menyimpan alat yang disematkan di halaman beranda, yang ditulis melalui `PUT /api/v1/preferences`.
| Kolom | Tipe | Catatan |
|---|---|---|
| `userId` | text | FK ke users, menghapus secara berantai. Primary key bersama `key` |
| `key` | text | Nama preferensi. Primary key bersama `userId` |
Dump database ini tidak berisi objek perpustakaan yang disimpan di `/data/files` atau status BullMQ yang tahan lama di Redis. Cadangkan dan pulihkan dengan prosedur terkoordinasi di [Keamanan & Pengerasan](/id/guide/security#backup-and-recovery).
Jangan menyalin direktori data PostgreSQL langsung dengan `tar`. Tulis nama volume awalan berdasarkan proyek, jadi selesaikan ID volume yang terpasang dari `docker inspect` atau platform penyimpanan Anda daripada menggunakan label literal `SnapOtter-pgdata`.
### Migrasi dari 1.x (SQLite) {#migrating-from-1-x-sqlite}
Memutakhirkan dari SnapOtter 1.x memiliki panduannya sendiri: lihat [Memutakhirkan dari 1.x ke 2.0](./upgrading). Singkatnya, gunakan kembali volume `/data` Anda yang ada dan 2.0 otomatis mendeteksi serta mengimpor `/data/snapotter.db` pada boot pertama (atau atur `SQLITE_MIGRATE_PATH` untuk menunjuk ke sana secara eksplisit). Cadangkan seluruh volume `/data` terlebih dahulu, bukan hanya `snapotter.db`: 1.x menggunakan mode SQLite WAL, sehingga kontainer yang dihentikan sering meninggalkan sebagian besar datanya di `snapotter.db-wal` di samping `snapotter.db` yang hampir kosong.