Цю сторінку перекладено машинним способом. Помітили помилку?Допоможіть її покращити.
Skip to content

База даних

SnapOtter використовує PostgreSQL 17 з Drizzle ORM (pg-core / node-postgres) для збереження даних. Схему визначено у apps/api/src/db/schema.ts.

З'єднання налаштовується через змінну середовища DATABASE_URL (за замовчуванням postgres://snapotter:snapotter@postgres:5432/snapotter). У Docker Compose контейнер Postgres зберігає свої дані в іменованому томі SnapOtter-pgdata. Запити обслуговує роль, яка може лише читати та записувати рядки; про це йдеться нижче в розділі Ролі з мінімальними привілеями.

Таблиці

users

Зберігає облікові записи користувачів. Створюється автоматично під час першого запуску з DEFAULT_USERNAME та DEFAULT_PASSWORD.

СтовпецьТипПримітки
iduuidПервинний ключ
usernamevarcharУнікальний, обов'язковий
passwordHashvarcharscrypt-хеш
rolevarcharadmin, editor або user
mustChangePasswordbooleanПрапорець примусового скидання пароля
createdAttimestampЧас створення
updatedAttimestampЧас останнього оновлення

sessions

Активні сесії входу. Кожен рядок пов'язує токен сесії з користувачем.

СтовпецьТипПримітки
idvarcharПервинний ключ (токен сесії)
userIduuidЗовнішній ключ на users.id
expiresAttimestampЧас закінчення терміну дії
createdAttimestampЧас створення

teams

Групи для організації користувачів. Адміністратори можуть призначати користувачів до команд.

СтовпецьТипОпис
iduuidПервинний ключ
namevarchar (унікальний, макс. 50 символів)Назва команди
createdAttimestampЧас створення

api_keys

API-ключі для програмного доступу. Необроблений ключ показується один раз під час створення; зберігається лише хеш.

СтовпецьТипПримітки
iduuidПервинний ключ
userIduuidЗовнішній ключ на users.id
keyHashvarcharscrypt-хеш ключа
namevarcharМітка, надана користувачем
createdAttimestampЧас створення
lastUsedAttimestampОновлюється під час кожного автентифікованого запиту

Ключі мають префікс si_, за яким слідують 96 шістнадцяткових символів (48 випадкових байтів).

pipelines

Збережені ланцюжки інструментів, які користувачі створюють в інтерфейсі.

СтовпецьТипПримітки
iduuidПервинний ключ
namevarcharНазва конвеєра
descriptionvarcharНеобов'язковий опис
stepsjsonbМасив об'єктів { toolId, settings }
createdAttimestampЧас створення

user_files

Постійна бібліотека файлів. За замовчуванням збережене редагування додається як незалежний кореневий рядок («зберегти як новий»: version 1, parentId null, тож оригінал лишається у списку) або як пов'язана з батьківською версія, коли ви перезаписуєте оригінал (parentId встановлено, version збільшено, замінюючи його). Стовпець toolChain записує застосовані інструменти.

СтовпецьТипОпис
iduuidПервинний ключ
userIduuidЗовнішній ключ на users (CASCADE DELETE)
originalNamevarcharОригінальна назва завантаженого файлу
storedNamevarcharНазва файлу на диску
mimeTypevarcharMIME-тип
sizeintegerРозмір файлу в байтах
widthintegerШирина зображення в пікселях
heightintegerВисота зображення в пікселях
versionintegerНомер версії (1 = оригінал)
parentIduuid або nullЗовнішній ключ на user_files (батьківська версія)
toolChainjsonbІдентифікатори інструментів, застосовані по порядку для створення цієї версії
createdAttimestampЧас створення

jobs

Відстежує завдання обробки для звітування про прогрес та очищення.

СтовпецьТипПримітки
iduuidПервинний ключ
typevarcharІдентифікатор інструмента чи конвеєра
statusvarcharqueued, processing, completed або failed
progressrealЧастка 0.0-1.0
inputFilesjsonbМасив шляхів до вхідних файлів
outputPathvarcharШлях до файлу результату
settingsjsonbВикористані налаштування інструмента
errorvarcharПовідомлення про помилку у разі невдачі
createdAttimestampЧас створення
completedAttimestampЧас завершення

settings

Сховище ключ-значення для загальносерверних налаштувань, які адміністратори можуть змінювати з інтерфейсу.

СтовпецьТипПримітки
keyvarcharПервинний ключ
valuevarcharЗначення налаштування
updatedAttimestampЧас останнього оновлення

roles

Кастомні ролі з деталізованими дозволами.

СтовпецьТипПримітки
iduuidПервинний ключ
namevarcharУнікальна назва ролі
descriptionvarcharНеобов'язковий опис
permissionsjsonbМасив рядків дозволів
createdAttimestampЧас створення

audit_log

Журнал дій, релевантних для безпеки.

СтовпецьТипПримітки
iduuidПервинний ключ
userIduuidЗовнішній ключ на users
actionvarcharТип дії
detailsjsonbДані, специфічні для дії
createdAttimestampЧас дії

user_preferences

Стан інтерфейсу для кожного користувача з ключем за назвою налаштування. Зберігає закріплені інструменти головної сторінки, які записуються через PUT /api/v1/preferences.

СтовпецьТипПримітки
userIdtextЗовнішній ключ на users, каскадне видалення. Первинний ключ разом із key
keytextНазва налаштування. Первинний ключ разом із userId
valuejsonbВміст налаштування
updatedAttimestampЧас останнього запису

Міграції

Drizzle відповідає за міграції схеми. Файли міграцій знаходяться у apps/api/drizzle/. Під час розробки:

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

У продакшені відкладені міграції застосовуються автоматично під час запуску.

Ролі з мінімальними привілеями

Дві ролі, дві задачі. DATABASE_URL обслуговує запити і має права SELECT, INSERT, UPDATE, DELETE на таблиці програми, а також USAGE та SELECT на їхні послідовності. Це весь перелік. Ця роль не може створити чи видалити таблицю, встановити розширення, виконати TRUNCATE, прочитати pg_authid, створити базу даних, змінити роль або торкнутися схеми drizzle, де зберігається історія міграцій.

DATABASE_MIGRATION_URL є привілейованою. Вона виконує міграції та видає права робочій ролі під час запуску, а потім закривається ще до того, як буде обслужено бодай один запит.

Compose і образ «все в одному» вже налаштовані саме так, зокрема й наявні інсталяції. Під час запуску SnapOtter створює робочу роль, якщо її немає, видає їй права, застосовує міграції, а тоді поширює права на таблиці, що існували раніше. Оновлення не потребує жодного ручного SQL.

Якщо залишити DATABASE_MIGRATION_URL порожнім, система працює в режимі однієї ролі, і DATABASE_URL виконує обидві задачі точно так, як до розділення. Це підтримувана конфігурація, а не застаріла. Саме вона доречна на керованому Postgres, де створення ролей часто вам недоступне.

Зовнішній і керований Postgres

На RDS, Supabase, Cloud SQL чи будь-якому кластері, який ви обслуговуєте самі, розділення вмикається за бажанням. Створіть робочу роль один раз:

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

Потім передайте SnapOtter обидва рядки з'єднання, що вказують на той самий хост, порт і базу даних:

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

На цьому все. SnapOtter сам видає права і повторно застосовує їх після кожної міграції, тож таблиця, додана в майбутньому випуску, буде охоплена без того, щоб хтось виконував для неї SQL.

Роль у DATABASE_MIGRATION_URL має бути власником таблиць SnapOtter, бо видавати права на таблицю може лише її власник. У наявній інсталяції це означає ту роль, під якою ви досі запускали SnapOtter, а не нову, створену спеціально для цього. Якщо вказати нову роль, яка нічим не володіє, запуск завершиться помилкою, що повідомляє саме про це. Ролі також потрібен CREATEROLE, щоб створювати та підтримувати робочу роль, і право створити схему drizzle.

Якщо вказати ту саму роль в обох URL, розділення вимикається, і SnapOtter прямо пише про це в журналі, а не вдає протилежне. Якщо ваш постачальник не дає жодної ролі, яка одночасно володіє таблицями і має CREATEROLE, працюйте в режимі однієї ролі.

Чому біт суперкористувача лишається недоторканим

SnapOtter ніколи не знімає SUPERUSER з ролі самостійно. В інсталяції, створеній до розділення, snapotter є єдиним суперкористувачем кластера, і його пониження залишило б кластер без жодного, що виправляється лише в однокористувацькому режимі із зупиненим сервером. Натомість захист дає перенесення довготривалого з'єднання на обмежену роль. Суперкористувач присутній у мережі кілька секунд під час запуску, а потім зникає.

Нові інсталяції «все в одному» такої проблеми не мають. Вони отримують три ролі: postgres (початковий суперкористувач, відсутній у всіх рядках з'єднання, які використовує SnapOtter), snapotter (NOSUPERUSER, володіє даними, підключається лише під час запуску) та snapotter_app (лише рядки, обслуговує запити).

Якщо ви все ж хочете понизити старішу роль snapotter, спершу створіть другого суперкористувача і увійдіть під ним, щоб переконатися, що він працює. Далі виконайте ALTER ROLE snapotter NOSUPERUSER.

Резервне копіювання та відновлення

Реляційна база даних знаходиться в томі SnapOtter-pgdata контейнера Postgres, а не в томі /data програми.

Логічна резервна копія з перевіркою (рекомендовано)

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

Обидві команди підключаються як snapotter, тобто власник, і так має лишатися. Робоча роль не бачить схему drizzle, тож дамп, знятий під нею, вийшов би неповним. --no-owner залишає відновлені об'єкти у власності того, хто запускає відновлення, тому запуск від власника ставить власність саме туди, де її очікують права. Одна пастка на новому кластері: pg_dump переносить права, але не ролі, які в них названі, тож створіть snapotter_app перед відновленням, інакше --exit-on-error зупиниться на першому GRANT. SnapOtter у будь-якому разі повторно застосує права під час наступного запуску.

Цей дамп бази даних не містить збережених об’єктів бібліотеки в /data/files або тривалому стані BullMQ у Redis. Створюйте резервні копії та відновлюйте їх за допомогою скоординованої процедури в Безпека та посилення.

Знімок холодного обсягу

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

Не копіюйте живий каталог даних PostgreSQL за допомогою tar. Створюйте префікси імен томів за проектом, тому вирішуйте ідентифікатори змонтованих томів із docker inspect або вашої платформи зберігання, а не припускайте буквальну мітку SnapOtter-pgdata.

Міграція з 1.x (SQLite)

Оновлення з SnapOtter 1.x має власний посібник: див. Оновлення з 1.x до 2.0. Коротко: повторно використайте свій наявний том /data, і 2.0 автоматично виявить та імпортує /data/snapotter.db під час першого запуску (або встановіть SQLITE_MIGRATE_PATH, щоб явно вказати на нього). Спершу зробіть резервну копію всього тому /data, а не лише snapotter.db: 1.x використовує режим SQLite WAL, тож зупинений контейнер часто залишає більшість своїх даних у snapotter.db-wal поруч із майже порожнім snapotter.db.