ваше сообщение коммита
This commit is contained in:
17
README.md
17
README.md
@@ -98,4 +98,19 @@ docker compose down -v
|
|||||||
## Примечания
|
## Примечания
|
||||||
|
|
||||||
- Загрузка модели qwen2.5:7b может занять некоторое время в зависимости от скорости интернета
|
- Загрузка модели qwen2.5:7b может занять некоторое время в зависимости от скорости интернета
|
||||||
- Для использования GPU Ollama требуются установленные драйверы NVIDIA и nvidia-container-toolkit
|
- Для использования GPU Ollama требуются установленные драйверы NVIDIA и nvidia-container-toolkit
|
||||||
|
|
||||||
|
## Важно! Если в контейнерах нет доступа к интернету
|
||||||
|
|
||||||
|
1. Откройте Docker Desktop → Settings → Docker Engine.
|
||||||
|
2. Добавьте строку:
|
||||||
|
"dns": ["8.8.8.8", "1.1.1.1"]
|
||||||
|
Пример:
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"dns": ["8.8.8.8", "1.1.1.1"]
|
||||||
|
}
|
||||||
|
3. Нажмите "Apply & Restart".
|
||||||
|
4. Перезапустите приложение:
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM node:20-alpine
|
FROM node:20-bullseye
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Устанавливаем зависимости, включая Python для node-gyp
|
# Устанавливаем зависимости, включая Python для node-gyp
|
||||||
RUN apk add --no-cache python3 make g++
|
RUN apt-get update && apt-get install -y python3 make g++ cmake openssl libssl-dev
|
||||||
|
|
||||||
# Копируем package.json и yarn.lock для установки зависимостей
|
# Копируем package.json и yarn.lock для установки зависимостей
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|||||||
6
backend/db/migrations/024_create_ipfs_publications.sql
Normal file
6
backend/db/migrations/024_create_ipfs_publications.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS ipfs_publications (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
cid TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
published_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# API Documentation
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
### POST /api/auth/refresh-session
|
|
||||||
|
|
||||||
Refreshes the user session.
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# Архитектура идентификаторов пользователей
|
|
||||||
|
|
||||||
## Общая структура
|
|
||||||
|
|
||||||
### Таблицы для хранения данных пользователей
|
|
||||||
|
|
||||||
Система идентификации пользователей построена на следующих таблицах:
|
|
||||||
|
|
||||||
1. **users** - Основная таблица пользователей
|
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Основной идентификатор пользователя
|
|
||||||
- `status` - Статус пользователя (active, blocked)
|
|
||||||
- `role` - Роль пользователя (user, admin)
|
|
||||||
- `created_at`, `updated_at` - Временные метки
|
|
||||||
- Поля `username`, `email` и `address` являются устаревшими и должны быть NULL
|
|
||||||
|
|
||||||
2. **user_identities** - Таблица идентификаторов пользователей
|
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
|
||||||
- `provider VARCHAR(50)` - Тип идентификатора (email, wallet, telegram, username)
|
|
||||||
- `provider_id VARCHAR(255)` - Значение идентификатора (должно быть в нижнем регистре для email и wallet)
|
|
||||||
- Уникальный составной ключ `(provider, provider_id)`
|
|
||||||
- Ограничение `CHECK (provider IN ('email', 'wallet', 'telegram', 'username'))` - запрещает тип 'guest'
|
|
||||||
|
|
||||||
3. **guest_user_mapping** - Таблица связи гостевых идентификаторов с пользователями
|
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
|
||||||
- `guest_id VARCHAR(255)` - Гостевой идентификатор
|
|
||||||
- `processed BOOLEAN` - Флаг обработки гостевых сообщений
|
|
||||||
- Уникальный ключ `guest_id`
|
|
||||||
|
|
||||||
4. **messages** - Таблица сообщений
|
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор сообщения
|
|
||||||
- `conversation_id INTEGER REFERENCES conversations(id)` - Ссылка на диалог
|
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Прямая ссылка на пользователя
|
|
||||||
- `content TEXT` - Содержание сообщения
|
|
||||||
- `sender_type`, `role` - Тип отправителя и роль (user/assistant)
|
|
||||||
|
|
||||||
5. **guest_messages** - Таблица гостевых сообщений
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор гостевого сообщения
|
|
||||||
- `guest_id VARCHAR(255)` - Идентификатор гостя
|
|
||||||
- `content TEXT` - Содержание сообщения
|
|
||||||
- `is_ai BOOLEAN` - Флаг, указывающий на сообщение от AI
|
|
||||||
|
|
||||||
## Процесс аутентификации и работа с гостевыми сообщениями
|
|
||||||
|
|
||||||
### Гостевой доступ
|
|
||||||
|
|
||||||
1. Гость (неаутентифицированный пользователь) начинает взаимодействие с системой
|
|
||||||
2. Для гостя генерируется уникальный `guest_id`, который сохраняется в localStorage браузера
|
|
||||||
3. Гостевые сообщения сохраняются в таблице `guest_messages` с привязкой к `guest_id`
|
|
||||||
4. Локально сообщения также хранятся в localStorage
|
|
||||||
|
|
||||||
### Аутентификация пользователя
|
|
||||||
|
|
||||||
1. Когда гость аутентифицируется (через email, wallet или telegram):
|
|
||||||
- Создается запись в таблице `users`
|
|
||||||
- Создается запись в таблице `user_identities` с соответствующим провайдером
|
|
||||||
- Гостевой ID сохраняется в таблице `guest_user_mapping` (не в user_identities)
|
|
||||||
2. После аутентификации система автоматически обрабатывает гостевые сообщения:
|
|
||||||
- Вызывается метод `linkGuestMessages`
|
|
||||||
- Создается новый диалог для гостевых сообщений
|
|
||||||
- Гостевые сообщения переносятся в таблицу `messages` с привязкой к пользователю
|
|
||||||
- Обработанные гостевые сообщения удаляются из `guest_messages`
|
|
||||||
- Запись в `guest_user_mapping` помечается как `processed = true`
|
|
||||||
|
|
||||||
### Объединение пользователей
|
|
||||||
|
|
||||||
Если пользователь аутентифицируется разными способами, система может объединить его данные:
|
|
||||||
|
|
||||||
1. Система проверяет связанных пользователей через `user_identities`
|
|
||||||
2. Если находятся связанные пользователи, вызывается метод `migrateUserData`
|
|
||||||
3. Данные от вторичных аккаунтов мигрируют к основному:
|
|
||||||
- Идентификаторы в таблице `user_identities`
|
|
||||||
- Гостевые связи в таблице `guest_user_mapping`
|
|
||||||
- Сообщения с прямым указанием `user_id`
|
|
||||||
- Диалоги
|
|
||||||
- Настройки
|
|
||||||
|
|
||||||
## Ограничения и правила
|
|
||||||
|
|
||||||
1. Тип провайдера `guest` запрещен в таблице `user_identities` (проверяется ограничением CHECK)
|
|
||||||
2. Гостевые идентификаторы хранятся только в таблице `guest_user_mapping`
|
|
||||||
3. Все идентификаторы email и wallet должны храниться в нижнем регистре
|
|
||||||
4. Таблица `messages` имеет прямую связь с пользователем через поле `user_id`
|
|
||||||
5. Сообщения всегда связаны с конкретным пользователем и диалогом
|
|
||||||
6. В таблице `users` поля `username`, `email` и `address` должны быть NULL
|
|
||||||
|
|
||||||
## Обработка ошибок
|
|
||||||
|
|
||||||
1. Если возникает ошибка при обработке гостевых сообщений, система:
|
|
||||||
|
|
||||||
- Логирует ошибку
|
|
||||||
- Продолжает попытки обработки при следующих авторизациях
|
|
||||||
- Не удаляет гостевые сообщения до успешной обработки
|
|
||||||
|
|
||||||
2. Если гостевые сообщения уже обработаны, повторная обработка пропускается
|
|
||||||
|
|
||||||
## Оптимизации
|
|
||||||
|
|
||||||
1. Индексы созданы для всех полей, используемых в запросах:
|
|
||||||
|
|
||||||
- `user_identities(user_id)`
|
|
||||||
- `user_identities(provider, provider_id)`
|
|
||||||
- `guest_user_mapping(guest_id)`
|
|
||||||
- `guest_user_mapping(user_id)`
|
|
||||||
- `messages(user_id)`
|
|
||||||
- `messages(conversation_id)`
|
|
||||||
|
|
||||||
2. Триггеры автоматически поддерживают целостность данных:
|
|
||||||
|
|
||||||
- Автоматическое заполнение `user_id` в таблице `messages`
|
|
||||||
- Очистка неиспользуемых полей в таблице `users`
|
|
||||||
|
|
||||||
3. Ограничения предотвращают некорректные данные:
|
|
||||||
- Запрет на использование провайдера `guest` в таблице `user_identities`
|
|
||||||
- Уникальность `guest_id` в таблице `guest_user_mapping`
|
|
||||||
- Ограничение допустимых значений для поля `provider`
|
|
||||||
|
|
||||||
## Функции для диагностики
|
|
||||||
|
|
||||||
1. **verify_migration_017()** - проверяет состояние гостевых идентификаторов
|
|
||||||
|
|
||||||
- `guest_identities_count` - количество гостевых идентификаторов в таблице user_identities
|
|
||||||
- `guest_mapping_count` - количество записей в таблице guest_user_mapping
|
|
||||||
- `missing_mappings` - количество гостевых ID, которые отсутствуют в guest_user_mapping
|
|
||||||
|
|
||||||
2. **verify_identity_data()** - проверяет общее состояние данных идентификаторов
|
|
||||||
- `users_with_address` - количество пользователей с заполненным полем address
|
|
||||||
- `users_with_email` - количество пользователей с заполненным полем email
|
|
||||||
- `wallet_identities` - количество идентификаторов wallet
|
|
||||||
- `email_identities` - количество идентификаторов email
|
|
||||||
- `telegram_identities` - количество идентификаторов telegram
|
|
||||||
- `duplicate_provider_ids` - количество дублирующихся идентификаторов
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Руководство по миграциям базы данных
|
|
||||||
|
|
||||||
## Общая информация
|
|
||||||
|
|
||||||
Система миграций базы данных предназначена для поддержания структуры базы данных в актуальном состоянии и обеспечения возможности обновления между версиями приложения.
|
|
||||||
|
|
||||||
## Структура миграций
|
|
||||||
|
|
||||||
Миграции размещены в папке `backend/db/migrations/` и именуются по схеме `XXX_descriptive_name.sql`, где XXX - порядковый номер миграции.
|
|
||||||
|
|
||||||
### Категории миграций
|
|
||||||
|
|
||||||
1. **Основные структурные миграции** (001-013) - создание базовых таблиц и первоначальной структуры
|
|
||||||
2. **Функциональные миграции** - изменения, связанные с конкретными функциями
|
|
||||||
3. **Рефакторинг и оптимизация** (019+) - улучшение существующей структуры
|
|
||||||
|
|
||||||
## Важные миграции
|
|
||||||
|
|
||||||
### 019_identity_system_refactor.sql
|
|
||||||
|
|
||||||
Комплексная миграция, объединяющая несколько предыдущих миграций (014-018) для улучшения системы идентификации пользователей:
|
|
||||||
|
|
||||||
- Создание таблицы `guest_user_mapping` для связи гостевых идентификаторов с пользователями
|
|
||||||
- Добавление прямой связи между сообщениями и пользователями через поле `user_id`
|
|
||||||
- Очистка дублирующихся данных между таблицами `users` и `user_identities`
|
|
||||||
- Нормализация формата идентификаторов (приведение к нижнему регистру)
|
|
||||||
- Добавление ограничений и триггеров для поддержания целостности данных
|
|
||||||
|
|
||||||
## Применение миграций
|
|
||||||
|
|
||||||
При развертывании новой версии приложения миграции применяются автоматически через скрипт `backend/db/run-migrations.js`. Порядок применения определяется порядковым номером в имени файла.
|
|
||||||
|
|
||||||
## Создание новых миграций
|
|
||||||
|
|
||||||
1. **Именование**: Используйте следующий свободный порядковый номер и описательное имя
|
|
||||||
2. **Идемпотентность**: Миграции должны быть безопасны для повторного выполнения
|
|
||||||
3. **Проверки**: Добавляйте проверки существования объектов перед их созданием
|
|
||||||
4. **Тестирование**: Проверяйте миграцию на тестовой базе данных перед применением
|
|
||||||
|
|
||||||
Пример правильной идемпотентной миграции:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Создание таблицы, если она не существует
|
|
||||||
CREATE TABLE IF NOT EXISTS example_table (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Добавление колонки, если она отсутствует
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.columns
|
|
||||||
WHERE table_name = 'example_table' AND column_name = 'new_column'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE example_table ADD COLUMN new_column INTEGER;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Архивация устаревших миграций
|
|
||||||
|
|
||||||
Устаревшие миграции, объединенные в более новые версии, перемещаются в папку `backend/db/migrations/archive/`. Для архивации используйте скрипт `backend/scripts/cleanup_migrations.sh`.
|
|
||||||
|
|
||||||
## Диагностические функции
|
|
||||||
|
|
||||||
Для проверки состояния базы данных и корректности миграций созданы следующие диагностические функции SQL:
|
|
||||||
|
|
||||||
- `verify_identity_system()` - проверка состояния системы идентификации пользователей
|
|
||||||
|
|
||||||
Пример использования:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT * FROM verify_identity_system();
|
|
||||||
```
|
|
||||||
@@ -36,7 +36,7 @@ const requireAuth = async (req, res, next) => {
|
|||||||
const address = authHeader.split(' ')[1];
|
const address = authHeader.split(' ')[1];
|
||||||
|
|
||||||
if (address.startsWith('0x')) {
|
if (address.startsWith('0x')) {
|
||||||
const result = await db.query(
|
const result = await db.getQuery()(
|
||||||
`
|
`
|
||||||
SELECT u.id, u.is_admin
|
SELECT u.id, u.is_admin
|
||||||
FROM users u
|
FROM users u
|
||||||
@@ -114,7 +114,7 @@ async function requireAdmin(req, res, next) {
|
|||||||
|
|
||||||
// Проверка через ID пользователя
|
// Проверка через ID пользователя
|
||||||
if (req.session.userId) {
|
if (req.session.userId) {
|
||||||
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [
|
const userResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [
|
||||||
req.session.userId,
|
req.session.userId,
|
||||||
]);
|
]);
|
||||||
if (userResult.rows.length > 0 && userResult.rows[0].role === USER_ROLES.ADMIN) {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === USER_ROLES.ADMIN) {
|
||||||
@@ -151,7 +151,7 @@ function requireRole(role) {
|
|||||||
|
|
||||||
// Проверка через ID пользователя
|
// Проверка через ID пользователя
|
||||||
if (req.session.userId) {
|
if (req.session.userId) {
|
||||||
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [
|
const userResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [
|
||||||
req.session.userId,
|
req.session.userId,
|
||||||
]);
|
]);
|
||||||
if (userResult.rows.length > 0 && userResult.rows[0].role === role) {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === role) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"@langchain/core": "0.3.0",
|
"@langchain/core": "0.3.0",
|
||||||
"@langchain/ollama": "^0.2.0",
|
"@langchain/ollama": "^0.2.0",
|
||||||
"@openzeppelin/contracts": "5.2.0",
|
"@openzeppelin/contracts": "5.2.0",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"connect-pg-simple": "^10.0.0",
|
"connect-pg-simple": "^10.0.0",
|
||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
@@ -43,6 +44,8 @@
|
|||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
"hnswlib-node": "^3.0.0",
|
"hnswlib-node": "^3.0.0",
|
||||||
"imap": "^0.8.19",
|
"imap": "^0.8.19",
|
||||||
|
"interface-store": "^6.0.2",
|
||||||
|
"ipfs-http-client": "^60.0.1",
|
||||||
"langchain": "^0.3.19",
|
"langchain": "^0.3.19",
|
||||||
"mailparser": "^3.7.2",
|
"mailparser": "^3.7.2",
|
||||||
"multer": "^1.4.5-lts.2",
|
"multer": "^1.4.5-lts.2",
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ router.get('/nonce', async (req, res) => {
|
|||||||
|
|
||||||
if (existingNonce.rows.length > 0) {
|
if (existingNonce.rows.length > 0) {
|
||||||
// Обновляем существующий nonce
|
// Обновляем существующий nonce
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
"UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL '15 minutes' WHERE identity_value = $2",
|
"UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL '15 minutes' WHERE identity_value = $2",
|
||||||
[nonce, address.toLowerCase()]
|
[nonce, address.toLowerCase()]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый nonce
|
// Создаем новый nonce
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
"INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL '15 minutes')",
|
"INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL '15 minutes')",
|
||||||
[address.toLowerCase(), nonce]
|
[address.toLowerCase(), nonce]
|
||||||
);
|
);
|
||||||
@@ -82,7 +82,7 @@ router.post('/verify', async (req, res) => {
|
|||||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||||
|
|
||||||
// Проверяем nonce
|
// Проверяем nonce
|
||||||
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [
|
const nonceResult = await db.getQuery()('SELECT nonce FROM nonces WHERE identity_value = $1', [
|
||||||
normalizedAddress,
|
normalizedAddress,
|
||||||
]);
|
]);
|
||||||
if (
|
if (
|
||||||
@@ -131,7 +131,7 @@ router.post('/verify', async (req, res) => {
|
|||||||
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
||||||
|
|
||||||
if (adminStatus) {
|
if (adminStatus) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
// Обновляем роль в БД, если она отличается от той, что была получена из verifyTelegramAuth
|
// Обновляем роль в БД, если она отличается от той, что была получена из verifyTelegramAuth
|
||||||
const currentRoleInDb = verificationResult.role === 'admin';
|
const currentRoleInDb = verificationResult.role === 'admin';
|
||||||
if (finalIsAdmin !== currentRoleInDb) {
|
if (finalIsAdmin !== currentRoleInDb) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', verificationResult.userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', verificationResult.userId]);
|
||||||
logger.info(`[telegram/verify] User role updated in DB for user ${verificationResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`);
|
logger.info(`[telegram/verify] User role updated in DB for user ${verificationResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -385,7 +385,7 @@ router.post('/email/verify-code', async (req, res) => {
|
|||||||
// Обновляем роль в БД, если она отличается от текущей
|
// Обновляем роль в БД, если она отличается от текущей
|
||||||
const currentRole = authResult.role === 'admin';
|
const currentRole = authResult.role === 'admin';
|
||||||
if (finalIsAdmin !== currentRole) {
|
if (finalIsAdmin !== currentRole) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', authResult.userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [finalIsAdmin ? 'admin' : 'user', authResult.userId]);
|
||||||
logger.info(`[email/verify-code] User role updated in DB for user ${authResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`);
|
logger.info(`[email/verify-code] User role updated in DB for user ${authResult.userId} to ${finalIsAdmin ? 'admin' : 'user'}`);
|
||||||
}
|
}
|
||||||
} catch (tokenCheckError) {
|
} catch (tokenCheckError) {
|
||||||
@@ -533,7 +533,7 @@ router.get('/check', async (req, res) => {
|
|||||||
identities = await identityService.getUserIdentities(req.session.userId);
|
identities = await identityService.getUserIdentities(req.session.userId);
|
||||||
|
|
||||||
// Проверяем роль пользователя
|
// Проверяем роль пользователя
|
||||||
const roleResult = await db.query('SELECT role FROM users WHERE id = $1', [
|
const roleResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [
|
||||||
req.session.userId,
|
req.session.userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -739,7 +739,7 @@ router.post('/wallet', async (req, res) => {
|
|||||||
|
|
||||||
// Обновляем роль пользователя в базе данных, если нужно
|
// Обновляем роль пользователя в базе данных, если нужно
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем идентификаторы
|
// Сохраняем идентификаторы
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const rpcProviderService = require('../services/rpcProviderService');
|
|||||||
const authTokenService = require('../services/authTokenService');
|
const authTokenService = require('../services/authTokenService');
|
||||||
const aiProviderSettingsService = require('../services/aiProviderSettingsService');
|
const aiProviderSettingsService = require('../services/aiProviderSettingsService');
|
||||||
const aiAssistant = require('../services/ai-assistant');
|
const aiAssistant = require('../services/ai-assistant');
|
||||||
|
const dns = require('node:dns').promises;
|
||||||
|
|
||||||
// Логируем версию ethers для отладки
|
// Логируем версию ethers для отладки
|
||||||
logger.info(`Ethers version: ${ethers.version || 'unknown'}`);
|
logger.info(`Ethers version: ${ethers.version || 'unknown'}`);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class AuthService {
|
|||||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||||
|
|
||||||
// Ищем пользователя по адресу в таблице user_identities
|
// Ищем пользователя по адресу в таблице user_identities
|
||||||
const userResult = await db.query(
|
const userResult = await db.getQuery()(
|
||||||
`
|
`
|
||||||
SELECT u.* FROM users u
|
SELECT u.* FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
@@ -60,11 +60,11 @@ class AuthService {
|
|||||||
|
|
||||||
// Если статус админа изменился, обновляем роль в базе данных
|
// Если статус админа изменился, обновляем роль в базе данных
|
||||||
if (user.role === 'admin' && !isAdmin) {
|
if (user.role === 'admin' && !isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]);
|
||||||
logger.info(`Updated user ${user.id} role to user (admin tokens no longer present)`);
|
logger.info(`Updated user ${user.id} role to user (admin tokens no longer present)`);
|
||||||
return { userId: user.id, isAdmin: false };
|
return { userId: user.id, isAdmin: false };
|
||||||
} else if (user.role !== 'admin' && isAdmin) {
|
} else if (user.role !== 'admin' && isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', user.id]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', user.id]);
|
||||||
logger.info(`Updated user ${user.id} role to admin (admin tokens found)`);
|
logger.info(`Updated user ${user.id} role to admin (admin tokens found)`);
|
||||||
return { userId: user.id, isAdmin: true };
|
return { userId: user.id, isAdmin: true };
|
||||||
}
|
}
|
||||||
@@ -76,14 +76,14 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Если пользователь не найден, создаем нового
|
// Если пользователь не найден, создаем нового
|
||||||
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'user',
|
'user',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const userId = newUserResult.rows[0].id;
|
const userId = newUserResult.rows[0].id;
|
||||||
|
|
||||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||||
[userId, 'wallet', normalizedAddress]
|
[userId, 'wallet', normalizedAddress]
|
||||||
);
|
);
|
||||||
@@ -94,7 +94,7 @@ class AuthService {
|
|||||||
|
|
||||||
// Если у пользователя есть админские токены, обновляем его роль
|
// Если у пользователя есть админские токены, обновляем его роль
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
logger.info(
|
logger.info(
|
||||||
`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`
|
`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`
|
||||||
);
|
);
|
||||||
@@ -301,7 +301,7 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сессию в БД
|
// Сохраняем сессию в БД
|
||||||
const result = await db.query(
|
const result = await db.getQuery()(
|
||||||
`UPDATE session
|
`UPDATE session
|
||||||
SET sess = $1
|
SET sess = $1
|
||||||
WHERE sid = $2`,
|
WHERE sid = $2`,
|
||||||
@@ -351,7 +351,7 @@ class AuthService {
|
|||||||
|
|
||||||
async getSession(sessionId) {
|
async getSession(sessionId) {
|
||||||
try {
|
try {
|
||||||
const result = await db.query('SELECT * FROM session WHERE sid = $1', [sessionId]);
|
const result = await db.getQuery()('SELECT * FROM session WHERE sid = $1', [sessionId]);
|
||||||
return result.rows[0];
|
return result.rows[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting session:', error);
|
console.error('Error getting session:', error);
|
||||||
@@ -363,7 +363,7 @@ class AuthService {
|
|||||||
async getLinkedWallet(userId) {
|
async getLinkedWallet(userId) {
|
||||||
logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`);
|
logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`);
|
||||||
try {
|
try {
|
||||||
const result = await db.query(
|
const result = await db.getQuery()(
|
||||||
`SELECT provider_id as address
|
`SELECT provider_id as address
|
||||||
FROM user_identities
|
FROM user_identities
|
||||||
WHERE user_id = $1 AND provider = 'wallet'`,
|
WHERE user_id = $1 AND provider = 'wallet'`,
|
||||||
@@ -422,7 +422,7 @@ class AuthService {
|
|||||||
const email = result.providerId;
|
const email = result.providerId;
|
||||||
|
|
||||||
// Проверяем, существует ли пользователь с таким email
|
// Проверяем, существует ли пользователь с таким email
|
||||||
const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
const userResult = await db.getQuery()('SELECT * FROM users WHERE id = $1', [userId]);
|
||||||
|
|
||||||
if (userResult.rows.length === 0) {
|
if (userResult.rows.length === 0) {
|
||||||
return { verified: false };
|
return { verified: false };
|
||||||
@@ -486,7 +486,7 @@ class AuthService {
|
|||||||
|
|
||||||
// Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы
|
// Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы
|
||||||
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
||||||
const existingUserResult = await db.query(
|
const existingUserResult = await db.getQuery()(
|
||||||
`SELECT u.*, ui.provider, ui.provider_id
|
`SELECT u.*, ui.provider, ui.provider_id
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
@@ -503,14 +503,14 @@ class AuthService {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Создаем нового пользователя для нового telegramId
|
// Создаем нового пользователя для нового telegramId
|
||||||
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'user',
|
'user',
|
||||||
]);
|
]);
|
||||||
userId = newUserResult.rows[0].id;
|
userId = newUserResult.rows[0].id;
|
||||||
isNewUser = true;
|
isNewUser = true;
|
||||||
|
|
||||||
// Добавляем Telegram идентификатор
|
// Добавляем Telegram идентификатор
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||||
[userId, 'telegram', telegramId]
|
[userId, 'telegram', telegramId]
|
||||||
);
|
);
|
||||||
@@ -522,7 +522,7 @@ class AuthService {
|
|||||||
|
|
||||||
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
||||||
if (session.guestId && isNewUser) {
|
if (session.guestId && isNewUser) {
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
[userId, session.guestId]
|
[userId, session.guestId]
|
||||||
);
|
);
|
||||||
@@ -555,7 +555,7 @@ class AuthService {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
try {
|
try {
|
||||||
// Находим userId по адресу
|
// Находим userId по адресу
|
||||||
const userResult = await db.query(
|
const userResult = await db.getQuery()(
|
||||||
`
|
`
|
||||||
SELECT u.id FROM users u
|
SELECT u.id FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
@@ -566,7 +566,7 @@ class AuthService {
|
|||||||
if (userResult.rows.length > 0) {
|
if (userResult.rows.length > 0) {
|
||||||
const userId = userResult.rows[0].id;
|
const userId = userResult.rows[0].id;
|
||||||
// Обновляем роль пользователя
|
// Обновляем роль пользователя
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
logger.info(`Updated user ${userId} role to admin based on token holdings`);
|
logger.info(`Updated user ${userId} role to admin based on token holdings`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -576,7 +576,7 @@ class AuthService {
|
|||||||
} else {
|
} else {
|
||||||
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
|
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
|
||||||
try {
|
try {
|
||||||
const userResult = await db.query(
|
const userResult = await db.getQuery()(
|
||||||
`
|
`
|
||||||
SELECT u.id, u.role FROM users u
|
SELECT u.id, u.role FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
@@ -586,7 +586,7 @@ class AuthService {
|
|||||||
|
|
||||||
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
|
||||||
const userId = userResult.rows[0].id;
|
const userId = userResult.rows[0].id;
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]);
|
||||||
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
|
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -624,7 +624,7 @@ class AuthService {
|
|||||||
|
|
||||||
// Удаляем старые идентификаторы
|
// Удаляем старые идентификаторы
|
||||||
for (const identity of identitiesToDelete) {
|
for (const identity of identitiesToDelete) {
|
||||||
await db.query('DELETE FROM user_identities WHERE id = $1', [identity.id]);
|
await db.getQuery()('DELETE FROM user_identities WHERE id = $1', [identity.id]);
|
||||||
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -640,7 +640,7 @@ class AuthService {
|
|||||||
*/
|
*/
|
||||||
async getUserIdentities(userId) {
|
async getUserIdentities(userId) {
|
||||||
try {
|
try {
|
||||||
const result = await db.query(
|
const result = await db.getQuery()(
|
||||||
'SELECT * FROM user_identities WHERE user_id = $1 ORDER BY created_at DESC',
|
'SELECT * FROM user_identities WHERE user_id = $1 ORDER BY created_at DESC',
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
@@ -705,7 +705,7 @@ class AuthService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Проверяем, существует ли уже такой идентификатор
|
// Проверяем, существует ли уже такой идентификатор
|
||||||
const existingResult = await db.query(
|
const existingResult = await db.getQuery()(
|
||||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||||
[provider, normalizedProviderId]
|
[provider, normalizedProviderId]
|
||||||
);
|
);
|
||||||
@@ -729,7 +729,7 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем новый идентификатор для пользователя
|
// Добавляем новый идентификатор для пользователя
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||||
VALUES ($1, $2, $3)`,
|
VALUES ($1, $2, $3)`,
|
||||||
[userId, provider, normalizedProviderId]
|
[userId, provider, normalizedProviderId]
|
||||||
@@ -742,7 +742,7 @@ class AuthService {
|
|||||||
|
|
||||||
// Обновляем роль пользователя в базе данных, если нужно
|
// Обновляем роль пользователя в базе данных, если нужно
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
logger.info(`[AuthService] Updated user ${userId} role to admin based on token holdings`);
|
logger.info(`[AuthService] Updated user ${userId} role to admin based on token holdings`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -791,7 +791,7 @@ class AuthService {
|
|||||||
logger.info(`[handleEmailVerification] Using temporary user ${userId}`);
|
logger.info(`[handleEmailVerification] Using temporary user ${userId}`);
|
||||||
} else {
|
} else {
|
||||||
// Создаем нового пользователя
|
// Создаем нового пользователя
|
||||||
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'user',
|
'user',
|
||||||
]);
|
]);
|
||||||
userId = newUserResult.rows[0].id;
|
userId = newUserResult.rows[0].id;
|
||||||
@@ -822,15 +822,15 @@ class AuthService {
|
|||||||
logger.info(`[handleEmailVerification] Role determined as: ${userRole}`);
|
logger.info(`[handleEmailVerification] Role determined as: ${userRole}`);
|
||||||
|
|
||||||
// Опционально: Обновить роль в таблице users
|
// Опционально: Обновить роль в таблице users
|
||||||
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
|
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
|
||||||
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
|
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
|
||||||
logger.info(`[handleEmailVerification] Updated user role in DB to ${userRole}`);
|
logger.info(`[handleEmailVerification] Updated user role in DB to ${userRole}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info(`[handleEmailVerification] No linked wallet found. Role remains 'user'.`);
|
logger.info(`[handleEmailVerification] No linked wallet found. Role remains 'user'.`);
|
||||||
// Если кошелька нет, проверяем текущую роль из базы (на случай, если она была admin ранее)
|
// Если кошелька нет, проверяем текущую роль из базы (на случай, если она была admin ранее)
|
||||||
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
|
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
|
||||||
if (currentUser.rows.length > 0) {
|
if (currentUser.rows.length > 0) {
|
||||||
userRole = currentUser.rows[0].role;
|
userRole = currentUser.rows[0].role;
|
||||||
}
|
}
|
||||||
@@ -839,7 +839,7 @@ class AuthService {
|
|||||||
logger.error(`[handleEmailVerification] Error checking admin role:`, roleCheckError);
|
logger.error(`[handleEmailVerification] Error checking admin role:`, roleCheckError);
|
||||||
// В случае ошибки берем текущую роль из базы или оставляем 'user'
|
// В случае ошибки берем текущую роль из базы или оставляем 'user'
|
||||||
try {
|
try {
|
||||||
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
|
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
|
||||||
if (currentUser.rows.length > 0) {
|
if (currentUser.rows.length > 0) {
|
||||||
userRole = currentUser.rows[0].role;
|
userRole = currentUser.rows[0].role;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ async function getAllAuthTokens() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveAllAuthTokens(authTokens) {
|
async function saveAllAuthTokens(authTokens) {
|
||||||
await db.query('DELETE FROM auth_tokens');
|
await db.getQuery()('DELETE FROM auth_tokens');
|
||||||
for (const token of authTokens) {
|
for (const token of authTokens) {
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO auth_tokens (name, address, network, min_balance) VALUES ($1, $2, $3, $4)',
|
'INSERT INTO auth_tokens (name, address, network, min_balance) VALUES ($1, $2, $3, $4)',
|
||||||
[token.name, token.address, token.network, token.minBalance]
|
[token.name, token.address, token.network, token.minBalance]
|
||||||
);
|
);
|
||||||
@@ -17,7 +17,7 @@ async function saveAllAuthTokens(authTokens) {
|
|||||||
|
|
||||||
async function upsertAuthToken(token) {
|
async function upsertAuthToken(token) {
|
||||||
const minBalance = token.minBalance == null ? 0 : Number(token.minBalance);
|
const minBalance = token.minBalance == null ? 0 : Number(token.minBalance);
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
`INSERT INTO auth_tokens (name, address, network, min_balance)
|
`INSERT INTO auth_tokens (name, address, network, min_balance)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
ON CONFLICT (address, network) DO UPDATE SET name=EXCLUDED.name, min_balance=EXCLUDED.min_balance`,
|
ON CONFLICT (address, network) DO UPDATE SET name=EXCLUDED.name, min_balance=EXCLUDED.min_balance`,
|
||||||
@@ -26,7 +26,7 @@ async function upsertAuthToken(token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAuthToken(address, network) {
|
async function deleteAuthToken(address, network) {
|
||||||
await db.query('DELETE FROM auth_tokens WHERE address = $1 AND network = $2', [address, network]);
|
await db.getQuery()('DELETE FROM auth_tokens WHERE address = $1 AND network = $2', [address, network]);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getAllAuthTokens, saveAllAuthTokens, upsertAuthToken, deleteAuthToken };
|
module.exports = { getAllAuthTokens, saveAllAuthTokens, upsertAuthToken, deleteAuthToken };
|
||||||
@@ -39,7 +39,7 @@ class EmailAuth {
|
|||||||
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
|
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
|
||||||
} else {
|
} else {
|
||||||
// Создаем временного пользователя, если нужно будет создать нового
|
// Создаем временного пользователя, если нужно будет создать нового
|
||||||
const userResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
const userResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'user',
|
'user',
|
||||||
]);
|
]);
|
||||||
userId = userResult.rows[0].id;
|
userId = userResult.rows[0].id;
|
||||||
@@ -148,7 +148,7 @@ class EmailAuth {
|
|||||||
finalUserId = session.tempUserId;
|
finalUserId = session.tempUserId;
|
||||||
logger.info(`[checkEmailVerification] Using temporary user ${finalUserId}`);
|
logger.info(`[checkEmailVerification] Using temporary user ${finalUserId}`);
|
||||||
} else {
|
} else {
|
||||||
const newUserResult = await db.query(
|
const newUserResult = await db.getQuery()(
|
||||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
||||||
['user']
|
['user']
|
||||||
);
|
);
|
||||||
@@ -172,9 +172,9 @@ class EmailAuth {
|
|||||||
logger.info(`[checkEmailVerification] Role for user ${finalUserId} determined as: ${userRole}`);
|
logger.info(`[checkEmailVerification] Role for user ${finalUserId} determined as: ${userRole}`);
|
||||||
|
|
||||||
// Опционально: Обновить роль в таблице users, если она отличается
|
// Опционально: Обновить роль в таблице users, если она отличается
|
||||||
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [finalUserId]);
|
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [finalUserId]);
|
||||||
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
|
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, finalUserId]);
|
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [userRole, finalUserId]);
|
||||||
logger.info(`[checkEmailVerification] Updated user role in DB to ${userRole}`);
|
logger.info(`[checkEmailVerification] Updated user role in DB to ${userRole}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -32,4 +32,6 @@ module.exports = {
|
|||||||
|
|
||||||
telegramBot,
|
telegramBot,
|
||||||
aiAssistant,
|
aiAssistant,
|
||||||
|
|
||||||
|
interfaceService: require('./interfaceService'),
|
||||||
};
|
};
|
||||||
|
|||||||
21
backend/services/interfaceService.mjs
Normal file
21
backend/services/interfaceService.mjs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { createHelia } from 'helia';
|
||||||
|
import { unixfs, globSource } from '@helia/unixfs';
|
||||||
|
import dns from 'node:dns/promises';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const db = require('../db');
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
export async function checkDomain(domain) {
|
||||||
|
try {
|
||||||
|
const records = await dns.resolveAny(domain);
|
||||||
|
return { records };
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ async function getAllRpcProviders() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveAllRpcProviders(rpcConfigs) {
|
async function saveAllRpcProviders(rpcConfigs) {
|
||||||
await db.query('DELETE FROM rpc_providers');
|
await db.getQuery()('DELETE FROM rpc_providers');
|
||||||
for (const cfg of rpcConfigs) {
|
for (const cfg of rpcConfigs) {
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO rpc_providers (network_id, rpc_url, chain_id) VALUES ($1, $2, $3)',
|
'INSERT INTO rpc_providers (network_id, rpc_url, chain_id) VALUES ($1, $2, $3)',
|
||||||
@@ -25,7 +25,7 @@ async function upsertRpcProvider(cfg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteRpcProvider(networkId) {
|
async function deleteRpcProvider(networkId) {
|
||||||
await db.query('DELETE FROM rpc_providers WHERE network_id = $1', [networkId]);
|
await db.getQuery()('DELETE FROM rpc_providers WHERE network_id = $1', [networkId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getAllRpcProviders, saveAllRpcProviders, upsertRpcProvider, deleteRpcProvider };
|
module.exports = { getAllRpcProviders, saveAllRpcProviders, upsertRpcProvider, deleteRpcProvider };
|
||||||
@@ -65,7 +65,7 @@ class SessionService {
|
|||||||
guestIdsToProcess.add(session.guestId);
|
guestIdsToProcess.add(session.guestId);
|
||||||
|
|
||||||
// Записываем связь с пользователем в новую таблицу
|
// Записываем связь с пользователем в новую таблицу
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
[userId, session.guestId]
|
[userId, session.guestId]
|
||||||
);
|
);
|
||||||
@@ -76,7 +76,7 @@ class SessionService {
|
|||||||
guestIdsToProcess.add(session.previousGuestId);
|
guestIdsToProcess.add(session.previousGuestId);
|
||||||
|
|
||||||
// Записываем связь с пользователем в новую таблицу
|
// Записываем связь с пользователем в новую таблицу
|
||||||
await db.query(
|
await db.getQuery()(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
[userId, session.previousGuestId]
|
[userId, session.previousGuestId]
|
||||||
);
|
);
|
||||||
@@ -95,9 +95,10 @@ class SessionService {
|
|||||||
session.processedGuestIds.push(guestId);
|
session.processedGuestIds.push(guestId);
|
||||||
|
|
||||||
// Помечаем guestId как обработанный в базе данных
|
// Помечаем guestId как обработанный в базе данных
|
||||||
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
|
await db.getQuery()(
|
||||||
guestId,
|
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
|
||||||
]);
|
[guestId]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
@@ -182,7 +183,10 @@ class SessionService {
|
|||||||
|
|
||||||
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
|
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
|
||||||
|
|
||||||
const result = await db.query('SELECT sess FROM session WHERE sid = $1', [sessionId]);
|
const result = await db.getQuery()(
|
||||||
|
'SELECT sess FROM session WHERE sid = $1',
|
||||||
|
[sessionId]
|
||||||
|
);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
logger.info(`[SessionService] No session found with ID ${sessionId}`);
|
logger.info(`[SessionService] No session found with ID ${sessionId}`);
|
||||||
|
|||||||
2773
backend/yarn.lock
2773
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./backend:/app
|
- ./backend:/app
|
||||||
- backend_node_modules:/app/node_modules
|
- backend_node_modules:/app/node_modules
|
||||||
|
- ./frontend/dist:/app/frontend_dist:ro
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=${NODE_ENV:-development}
|
- NODE_ENV=${NODE_ENV:-development}
|
||||||
- PORT=${PORT:-8000}
|
- PORT=${PORT:-8000}
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
VITE_APP_ETHEREUM_NETWORK_URL=https://your-ethereum-network-url
|
|
||||||
|
|
||||||
VITE_API_URL=http://localhost:8000
|
VITE_API_URL=http://localhost:8000
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ai-settings settings-panel">
|
<div class="ai-settings settings-panel">
|
||||||
<h2>Интеграции</h2>
|
<h2>Интеграции</h2>
|
||||||
<div class="integration-blocks" v-if="!showProvider && !showEmailSettings && !showTelegramSettings">
|
<div class="integration-blocks" v-if="!showProvider && !showEmailSettings && !showTelegramSettings && !showDbSettings">
|
||||||
<div class="integration-block">
|
<div class="integration-block">
|
||||||
<h3>OpenAI</h3>
|
<h3>OpenAI</h3>
|
||||||
<p>Интеграция с OpenAI (GPT-4, GPT-3.5 и др.).</p>
|
<p>Интеграция с OpenAI (GPT-4, GPT-3.5 и др.).</p>
|
||||||
@@ -32,6 +32,11 @@
|
|||||||
<p>Интеграция с Email для отправки писем и уведомлений.</p>
|
<p>Интеграция с Email для отправки писем и уведомлений.</p>
|
||||||
<button class="details-btn" @click="showEmailSettings = true">Подробнее</button>
|
<button class="details-btn" @click="showEmailSettings = true">Подробнее</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="integration-block">
|
||||||
|
<h3>База данных</h3>
|
||||||
|
<p>Интеграция с PostgreSQL для хранения данных приложения и управления настройками.</p>
|
||||||
|
<button class="details-btn" @click="showDbSettings = true">Подробнее</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AIProviderSettings
|
<AIProviderSettings
|
||||||
v-if="showProvider"
|
v-if="showProvider"
|
||||||
@@ -46,6 +51,7 @@
|
|||||||
/>
|
/>
|
||||||
<TelegramSettingsView v-if="showTelegramSettings" @cancel="showTelegramSettings = false" />
|
<TelegramSettingsView v-if="showTelegramSettings" @cancel="showTelegramSettings = false" />
|
||||||
<EmailSettingsView v-if="showEmailSettings" @cancel="showEmailSettings = false" />
|
<EmailSettingsView v-if="showEmailSettings" @cancel="showEmailSettings = false" />
|
||||||
|
<DatabaseSettingsView v-if="showDbSettings" @cancel="showDbSettings = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -54,9 +60,11 @@ import { ref } from 'vue';
|
|||||||
import AIProviderSettings from './AIProviderSettings.vue';
|
import AIProviderSettings from './AIProviderSettings.vue';
|
||||||
import TelegramSettingsView from './TelegramSettingsView.vue';
|
import TelegramSettingsView from './TelegramSettingsView.vue';
|
||||||
import EmailSettingsView from './EmailSettingsView.vue';
|
import EmailSettingsView from './EmailSettingsView.vue';
|
||||||
|
import DatabaseSettingsView from './DatabaseSettingsView.vue';
|
||||||
const showProvider = ref(null);
|
const showProvider = ref(null);
|
||||||
const showTelegramSettings = ref(false);
|
const showTelegramSettings = ref(false);
|
||||||
const showEmailSettings = ref(false);
|
const showEmailSettings = ref(false);
|
||||||
|
const showDbSettings = ref(false);
|
||||||
|
|
||||||
const providerLabels = {
|
const providerLabels = {
|
||||||
openai: {
|
openai: {
|
||||||
|
|||||||
115
frontend/src/views/settings/DomainConnectBlock.vue
Normal file
115
frontend/src/views/settings/DomainConnectBlock.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div class="domain-block">
|
||||||
|
<h3>Подключение домена</h3>
|
||||||
|
<p>Укажите свой домен, чтобы привязать его к опубликованному в IPFS фронтенду. Следуйте инструкции ниже для настройки DNS.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<input v-model="domain" class="form-control" placeholder="example.com" />
|
||||||
|
<button class="btn-primary" :disabled="loading || !domain" @click="checkDomain">
|
||||||
|
{{ loading ? 'Проверка...' : 'Проверить' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="error" class="error">Ошибка: {{ error }}</div>
|
||||||
|
<div v-if="success" class="success">DNS-записи найдены: <pre>{{ records }}</pre></div>
|
||||||
|
<div class="instruction">
|
||||||
|
<b>Инструкция по подключению:</b>
|
||||||
|
<ol>
|
||||||
|
<li>Опубликуйте фронтенд в IPFS и получите CID.</li>
|
||||||
|
<li>В панели управления доменом создайте DNS-запись типа <b>CNAME</b> или <b>TXT</b> (или используйте IPFS gateway).</li>
|
||||||
|
<li>Для CNAME: укажите <code>yourdomain.com CNAME gateway.ipfs.io</code> или аналогичный шлюз.</li>
|
||||||
|
<li>Для TXT: <code>_dnslink.yourdomain.com TXT dnslink=/ipfs/<ваш CID></code></li>
|
||||||
|
<li>Дождитесь обновления DNS и проверьте домен через эту форму.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import axios from '../../api/axios';
|
||||||
|
|
||||||
|
const domain = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref('');
|
||||||
|
const success = ref(false);
|
||||||
|
const records = ref('');
|
||||||
|
|
||||||
|
async function checkDomain() {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
success.value = false;
|
||||||
|
records.value = '';
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post('/api/settings/interface/check-domain', { domain: domain.value });
|
||||||
|
if (data.success && data.records) {
|
||||||
|
records.value = JSON.stringify(data.records, null, 2);
|
||||||
|
success.value = true;
|
||||||
|
} else {
|
||||||
|
error.value = data.error || 'DNS-записи не найдены';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e.response?.data?.error || e.message || 'Ошибка запроса';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.domain-block {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.btn-primary:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: var(--color-success, #2e7d32);
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: var(--color-danger, #c62828);
|
||||||
|
}
|
||||||
|
.instruction {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
font-size: 0.95em;
|
||||||
|
color: #555;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.95em;
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -18,14 +18,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Другие настройки интерфейса можно добавить сюда -->
|
<DomainConnectBlock />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { getFromStorage, setToStorage } from '../../utils/storage'; // Путь к utils может отличаться
|
import { getFromStorage, setToStorage } from '../../utils/storage'; // Путь к utils может отличаться
|
||||||
|
import DomainConnectBlock from './DomainConnectBlock.vue';
|
||||||
// TODO: Импортировать API для сохранения, если нужно
|
// TODO: Импортировать API для сохранения, если нужно
|
||||||
|
|
||||||
const selectedLanguage = ref(getFromStorage('userLanguage', 'ru'));
|
const selectedLanguage = ref(getFromStorage('userLanguage', 'ru'));
|
||||||
|
|||||||
1
frontend/src/views/settings/PublishToIPFSBlock.vue
Normal file
1
frontend/src/views/settings/PublishToIPFSBlock.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!-- Компонент удалён как неактуальный -->
|
||||||
Reference in New Issue
Block a user