Questa pagina è stata tradotta automaticamente. Hai notato un errore?Aiutaci a migliorarla.
Skip to content

Database

SnapOtter usa PostgreSQL 17 con Drizzle ORM (pg-core / node-postgres) per la persistenza dei dati. Lo schema è definito in apps/api/src/db/schema.ts.

La connessione è configurata tramite la variabile d'ambiente DATABASE_URL (predefinita postgres://snapotter:snapotter@postgres:5432/snapotter). In Docker Compose, il container Postgres memorizza i suoi dati nel volume denominato SnapOtter-pgdata. Le richieste vengono servite con un ruolo che può soltanto leggere e scrivere righe, come descritto più avanti in Ruoli con privilegi minimi.

Tabelle

users

Memorizza gli account utente. Creata automaticamente al primo avvio da DEFAULT_USERNAME e DEFAULT_PASSWORD.

ColonnaTipoNote
iduuidChiave primaria
usernamevarcharUnivoco, obbligatorio
passwordHashvarcharHash scrypt
rolevarcharadmin, editor o user
mustChangePasswordbooleanFlag di reimpostazione forzata della password
createdAttimestampData di creazione
updatedAttimestampData dell'ultimo aggiornamento

sessions

Sessioni di login attive. Ogni riga associa un token di sessione a un utente.

ColonnaTipoNote
idvarcharChiave primaria (token di sessione)
userIduuidChiave esterna verso users.id
expiresAttimestampData di scadenza
createdAttimestampData di creazione

teams

Gruppi per organizzare gli utenti. Gli amministratori possono assegnare gli utenti ai team.

ColonnaTipoDescrizione
iduuidChiave primaria
namevarchar (univoco, max 50 caratteri)Nome del team
createdAttimestampData di creazione

api_keys

Chiavi API per l'accesso programmatico. La chiave grezza viene mostrata una sola volta alla creazione; viene memorizzato solo l'hash.

ColonnaTipoNote
iduuidChiave primaria
userIduuidChiave esterna verso users.id
keyHashvarcharHash scrypt della chiave
namevarcharEtichetta fornita dall'utente
createdAttimestampData di creazione
lastUsedAttimestampAggiornata a ogni richiesta autenticata

Le chiavi hanno il prefisso si_ seguito da 96 caratteri esadecimali (48 byte casuali).

pipelines

Catene di strumenti salvate che gli utenti creano nell'interfaccia.

ColonnaTipoNote
iduuidChiave primaria
namevarcharNome della pipeline
descriptionvarcharDescrizione facoltativa
stepsjsonbArray di oggetti { toolId, settings }
createdAttimestampData di creazione

user_files

Libreria di file persistente. Per impostazione predefinita, una modifica salvata viene inserita come riga radice indipendente ("salva come nuovo": version 1, parentId null, così l'originale resta elencato), oppure come versione collegata al genitore quando sovrascrivi l'originale (parentId impostato, version incrementata, sostituendolo). La colonna toolChain registra gli strumenti applicati.

ColonnaTipoDescrizione
iduuidChiave primaria
userIduuidFK verso users (CASCADE DELETE)
originalNamevarcharNome del file di caricamento originale
storedNamevarcharNome del file su disco
mimeTypevarcharTipo MIME
sizeintegerDimensione del file in byte
widthintegerLarghezza dell'immagine in px
heightintegerAltezza dell'immagine in px
versionintegerNumero di versione (1 = originale)
parentIduuid o nullFK verso user_files (versione genitore)
toolChainjsonbID degli strumenti applicati in ordine per produrre questa versione
createdAttimestampData di creazione

jobs

Traccia i job di elaborazione per la segnalazione dell'avanzamento e la pulizia.

ColonnaTipoNote
iduuidChiave primaria
typevarcharIdentificatore dello strumento o della pipeline
statusvarcharqueued, processing, completed o failed
progressrealFrazione 0.0-1.0
inputFilesjsonbArray dei percorsi dei file di input
outputPathvarcharPercorso del file risultato
settingsjsonbImpostazioni dello strumento utilizzate
errorvarcharMessaggio di errore in caso di fallimento
createdAttimestampData di creazione
completedAttimestampData di completamento

settings

Archivio chiave-valore per le impostazioni a livello di server che gli amministratori possono modificare dall'interfaccia.

ColonnaTipoNote
keyvarcharChiave primaria
valuevarcharValore dell'impostazione
updatedAttimestampData dell'ultimo aggiornamento

roles

Ruoli personalizzati con permessi granulari.

ColonnaTipoNote
iduuidChiave primaria
namevarcharNome univoco del ruolo
descriptionvarcharDescrizione facoltativa
permissionsjsonbArray di stringhe di permesso
createdAttimestampData di creazione

audit_log

Registro delle azioni rilevanti per la sicurezza.

ColonnaTipoNote
iduuidChiave primaria
userIduuidFK verso users
actionvarcharTipo di azione
detailsjsonbDati specifici dell'azione
createdAttimestampData dell'azione

user_preferences

Stato dell'interfaccia per singolo utente, indicizzato per nome della preferenza. Conserva gli strumenti fissati della pagina iniziale, scritti tramite PUT /api/v1/preferences.

ColonnaTipoNote
userIdtextFK verso users, con eliminazione a cascata. Chiave primaria insieme a key
keytextNome della preferenza. Chiave primaria insieme a userId
valuejsonbContenuto della preferenza
updatedAttimestampUltima scrittura

Migrazioni

Drizzle gestisce le migrazioni dello schema. I file di migrazione risiedono in apps/api/drizzle/. Durante lo sviluppo:

bash
cd apps/api
npx drizzle-kit generate   # generate a migration from schema changes
npx drizzle-kit migrate    # apply pending migrations

In produzione, le migrazioni in sospeso vengono applicate automaticamente all'avvio.

Ruoli con privilegi minimi

Due ruoli, due compiti. DATABASE_URL serve le richieste e detiene SELECT, INSERT, UPDATE, DELETE sulle tabelle dell'app, più USAGE e SELECT sulle relative sequenze. L'elenco finisce qui. Non può creare o eliminare una tabella, installare un'estensione, eseguire TRUNCATE, leggere pg_authid, creare un database, modificare un ruolo o toccare lo schema drizzle, dove risiede la cronologia delle migrazioni.

DATABASE_MIGRATION_URL è quello privilegiato. Esegue le migrazioni e assegna i permessi al ruolo di runtime durante l'avvio, poi si chiude prima che venga servita una singola richiesta.

Compose e l'immagine all-in-one sono già configurati così, installazioni esistenti comprese. All'avvio SnapOtter crea il ruolo di runtime se manca, gli assegna i permessi, esegue le migrazioni e poi estende i permessi alle tabelle già presenti. L'aggiornamento non richiede alcun SQL manuale.

Se lasci vuoto DATABASE_MIGRATION_URL, si passa alla modalità a ruolo unico, con DATABASE_URL che svolge entrambi i compiti esattamente come faceva prima della separazione. È una configurazione supportata, non deprecata. È la scelta giusta su Postgres gestito, dove spesso la creazione dei ruoli non spetta a te.

Postgres esterno e gestito

Su RDS, Supabase, Cloud SQL o su qualsiasi cluster che gestisci tu, la separazione è facoltativa. Crea il ruolo di runtime una sola volta:

sql
CREATE ROLE snapotter_app LOGIN PASSWORD 'choose-a-strong-password'
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;

Poi passa a SnapOtter entrambe le stringhe di connessione, puntate allo stesso host, alla stessa porta e allo stesso database:

bash
DATABASE_URL=postgres://snapotter_app:[email protected]:5432/snapotter
DATABASE_MIGRATION_URL=postgres://snapotter:[email protected]:5432/snapotter

Fermati qui. SnapOtter applica i permessi da sé e li riapplica dopo ogni migrazione, così una tabella aggiunta da una versione futura è coperta senza che nessuno debba eseguire SQL apposta.

Il ruolo indicato in DATABASE_MIGRATION_URL deve essere proprietario delle tabelle di SnapOtter, perché solo il proprietario di una tabella può concedere permessi su di essa. In un'installazione esistente si tratta del ruolo con cui hai eseguito SnapOtter finora, non di uno nuovo creato allo scopo. Se lo fai puntare a un ruolo nuovo che non possiede nulla, l'avvio fallisce con un errore che dice esattamente questo. Servono inoltre CREATEROLE, per creare e mantenere il ruolo di runtime, e il diritto di creare lo schema drizzle.

Se indichi lo stesso ruolo in entrambi gli URL la separazione è disattivata, e SnapOtter lo scrive nel log invece di far finta di nulla. Se il tuo provider non ti offre un ruolo capace sia di possedere le tabelle sia di avere CREATEROLE, usa la modalità a ruolo unico.

Perché il bit di superuser resta invariato

SnapOtter non rimuove mai da solo SUPERUSER da un ruolo. In un'installazione creata prima della separazione, snapotter è l'unico superuser del cluster, e degradarlo lascerebbe il cluster senza nessuno, con un recupero possibile solo tramite la modalità single-user a server fermo. A garantire la protezione è invece lo spostamento della connessione a lunga durata sul ruolo limitato. Il superuser resta sulla linea per i pochi secondi dell'avvio e poi sparisce.

Le nuove installazioni all-in-one non hanno questo problema. Ottengono tre ruoli: postgres (superuser di bootstrap, assente da ogni stringa di connessione usata da SnapOtter), snapotter (NOSUPERUSER, possiede i dati, si connette solo all'avvio) e snapotter_app (solo righe, serve le richieste).

Per degradare comunque un vecchio snapotter, crea prima un secondo superuser e accedi con quello per verificare che funzioni. Poi ALTER ROLE snapotter NOSUPERUSER.

Backup e ripristino

Il database relazionale si trova nel volume SnapOtter-pgdata del contenitore Postgres, non nel volume /data dell'app.

Backup logico con convalida (consigliato)

bash
# Dump into PostgreSQL's portable custom archive format
docker exec SnapOtter-postgres \
  pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
test -s snapotter.dump
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null

# Restore into a fresh/disposable target first and fail on the first SQL error
docker exec -i SnapOtter-postgres \
  pg_restore --exit-on-error --clean --if-exists --no-owner \
  -U snapotter -d snapotter < snapotter.dump

Entrambi i comandi si connettono come snapotter, il proprietario, e devono continuare a farlo. Il ruolo di runtime non vede lo schema drizzle, quindi un dump eseguito con quel ruolo risulterebbe incompleto. --no-owner lascia gli oggetti ripristinati di proprietà di chi esegue il ripristino, quindi eseguirlo come proprietario colloca la proprietà dove i permessi se l'aspettano. Un dettaglio da tenere presente su un cluster nuovo: pg_dump porta con sé i permessi ma non i ruoli che vi sono nominati, quindi crea snapotter_app prima del ripristino, altrimenti --exit-on-error si ferma al primo GRANT. In ogni caso SnapOtter riapplica i permessi al successivo avvio.

Questo dump del database non contiene oggetti di libreria salvati in /data/files o lo stato BullMQ durevole in Redis. Effettuare il backup e il ripristino di quelli con la procedura coordinata in Sicurezza e rafforzamento.

Istantanea del volume freddo

bash
# Stop every service first, then use your storage platform to snapshot the
# PostgreSQL, app-data, and Redis volumes as one crash-consistent set.
docker compose -f docker/docker-compose.yml stop

Non copiare una directory di dati PostgreSQL live con tar. Componi i prefissi dei nomi dei volumi in base al progetto, quindi risolvi gli ID dei volumi montati da docker inspect o dalla tua piattaforma di archiviazione anziché assumere l'etichetta letterale SnapOtter-pgdata.

Migrazione dalla 1.x (SQLite)

L'aggiornamento da SnapOtter 1.x ha una guida dedicata: vedi Aggiornamento dalla 1.x alla 2.0. In breve, riutilizza il tuo volume /data esistente e la 2.0 rileva e importa automaticamente /data/snapotter.db al primo avvio (oppure imposta SQLITE_MIGRATE_PATH per puntarvi esplicitamente). Esegui prima il backup dell'intero volume /data, non solo di snapotter.db: la 1.x usa la modalità WAL di SQLite, quindi un container arrestato lascia spesso la maggior parte dei suoi dati in snapotter.db-wal accanto a un snapotter.db quasi vuoto.