Дата: 2026-07-26
Заменить устройство-локальное хранение пользователей, истории чатов и выбранной модели на PostgreSQL. Приложение должно запускаться вместе с базой через Docker Compose, а вход и API чата должны работать с данными реального пользователя из базы.
docker-compose.yml определяет два сервиса:
db: PostgreSQL 16, доступный с хоста на 5433:5432;app: Next.js-сервис, собранный из локального Dockerfile и доступный на 3000:3000.Параметры PostgreSQL фиксированы:
POSTGRES_USER=ai-control-chat-ui
POSTGRES_PASSWORD=ai-control-chat-ui
POSTGRES_DB=ai_control_chat_ui
DATABASE_URL=postgresql://ai-control-chat-ui:ai-control-chat-ui@db:5432/ai_control_chat_ui
db получает healthcheck через pg_isready. app запускается после перехода базы в healthy-состояние.
Dockerfile использует многоступенчатую сборку Node.js 22 Alpine. Next.js собирается с output: "standalone"; production-образ содержит standalone-сервер, публичные файлы, миграции и скрипты запуска, но не исходные dev-зависимости.
Миграции расположены в db/migrations, имеют монотонные имена 001_*.sql, и применяются Node-скриптом в транзакции. Таблица schema_migrations хранит имена уже применённых файлов; повторный запуск не изменяет уже применённую схему.
Перед запуском приложения контейнер выполняет миграции и безопасный idempotent seed. Seed создаёт или обновляет тестового пользователя:
email: demo@ai-control.local
password: Demo1234!
Пароль сохраняется только как bcrypt-хеш. В production пароль seed-пользователя переопределяется через AUTH_PASSWORD; значение по умолчанию предназначено только для локального Docker-сценария.
| Поле | Тип | Ограничение |
|---|---|---|
| id | UUID | primary key |
| text | unique, not null | |
| password_hash | text | not null |
| last_model_id | text | not null, default chatgpt, check: chatgpt, deepseek, qwen |
| created_at | timestamptz | not null |
| updated_at | timestamptz | not null |
| Поле | Тип | Ограничение |
|---|---|---|
| id | UUID | primary key |
| user_id | UUID | foreign key to users, cascade delete |
| title | text | not null |
| model_id | text | valid model check |
| created_at | timestamptz | not null |
| updated_at | timestamptz | not null |
Индекс conversations(user_id, updated_at desc) служит загрузке боковой панели.
| Поле | Тип | Ограничение |
|---|---|---|
| id | UUID | primary key |
| conversation_id | UUID | foreign key to conversations, cascade delete |
| role | text | user или assistant |
| content | text | not null |
| status | text | streaming, complete, error, stopped |
| created_at | timestamptz | not null |
Индекс chat_messages(conversation_id, created_at) служит загрузке истории и построению контекста.
lib/db содержит единственный pg.Pool, репозитории и транзакционные функции. SQL всегда параметризован; Route Handlers не содержат SQL-строк.
Репозитории:
users: поиск по email/ID, bcrypt-проверка пароля, чтение и обновление last_model_id;conversations: создание, загрузка только диалогов владельца, удаление и обновление модели/заголовка;messages: добавление пользовательского сообщения, создание assistant-shell, дописывание потока и финализация статуса.В тестах репозитории получают зависимость Queryable, что позволяет проверять SQL-логику без реальной сети PostgreSQL. Интеграционный Docker smoke-сценарий проверяет миграции и подключение к настоящей базе.
POST /api/auth/login получает email и пароль, запрашивает пользователя в PostgreSQL и сравнивает пароль с bcrypt-хешем. При успехе JWT подписывается существующим JWT_SECRET; sub — UUID строки users.id, email — email из базы.
GET /api/auth/me проверяет Bearer JWT и дополнительно читает пользователя по sub. Удалённый пользователь или несовпадение email возвращает 401.
Никакие логины и пароли не поступают в браузер кроме введённого пользователем пароля при входе. JWT продолжает жить 24 часа и передаётся только как Authorization: Bearer <token>.
Возвращает диалоги текущего пользователя с сообщениями, отсортированные по updated_at по убыванию. Перед возвратом любые сохранённые streaming-сообщения переводятся в stopped, чтобы после перезапуска не возникал вечный индикатор генерации.
Принимает { model: ModelId }, проверяет модель и сохраняет users.last_model_id. Значение применяется к следующему новому чату и восстанавливается после перезагрузки.
Принимает только:
{
conversationId: string;
model: "chatgpt" | "deepseek" | "qwen";
content?: string;
retry?: boolean;
}
Для обычного сообщения сервер проверяет, что диалог принадлежит текущему пользователю, создаёт диалог при новом UUID, сохраняет пользовательское сообщение, строит серверный контекст в пределах 100 сообщений/32 000 символов на сообщение/128 000 символов суммарно и запускает Gateway.
Для retry сервер использует только последнее валидное пользовательское сообщение перед финальным error/stopped assistant-сообщением. Новой пользовательской записи не создаётся.
Перед отдачей потока сервер создаёт assistant-shell со статусом streaming. Поток одновременно передаётся клиенту и накапливается на сервере. При нормальном окончании shell обновляется до complete; при отмене — до stopped; при ошибке Gateway — до error с безопасным текстом. Изменение updated_at и выбранной модели диалога выполняется в той же транзакции, что и создание сообщения.
Gateway получает только нормализованные сообщения и отдельный AI_GATEWAY_API_KEY. JWT клиента в Gateway не передаётся.
После успешной проверки JWT AuthGate вызывает GET /api/conversations, а хук чата использует сервер как единственный источник истины. Локальный storage больше не содержит историю чатов и модель; его прежние записи игнорируются.
При выборе модели клиент оптимистично обновляет UI и вызывает PATCH /api/me/model. Создание, удаление, отправка и retry изменяют UI оптимистично, затем синхронизируются с API; при ошибке происходит контролируемый reload истории.
conversationId возвращает 404, не раскрывая данные другого пользователя.401.400.503.5432; с хоста он доступен только на 5433./api/me, /api/conversations, модели и стриминга чата.docker compose up --build, healthcheck БД, миграции/seed, успешный вход и сохранение диалога.npm test, npm run lint, npx tsc --noEmit, npm run build и docker compose config.docker compose up --build поднимает приложение и PostgreSQL с БД на localhost:5433.