diff --git a/.gitignore b/.gitignore index 620f960..70e0a96 100644 --- a/.gitignore +++ b/.gitignore @@ -202,4 +202,14 @@ backend/test_*.js # Environment specific files backend/.env.local -backend/.env.production \ No newline at end of file +backend/.env.production + +# Server management scripts - may contain sensitive information +scripts/clean-server.sh +scripts/export-template-for-release.sh + +# Docker data and release archives - не коммитить в Git! +docker-data/ +dle-template.tar.gz +dle-template.tar.gz.part-* +dle-template.tar.gz.join.sh \ No newline at end of file diff --git a/README.en.md b/README.en.md index 3bde1cf..609b299 100644 --- a/README.en.md +++ b/README.en.md @@ -36,7 +36,8 @@ curl -fsSL https://raw.githubusercontent.com/VC-HB3-Accelerator/DLE/main/setup.s The script automatically downloads the latest artifacts from the release and deploys `docker-data`. ### 📦 Releases and artifacts -- [Release v1.0.0](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.0) — contains an archive split into parts (`dle-template.tar.gz.part-*`) that is installed together with the script. +- [Release v1.0.1](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.1) (Latest) — contains the complete application template with Docker images, volumes, and encryption key. Archive is split into parts (`dle-template.tar.gz.part-*`) for easy download. +- [Release v1.0.0](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.0) — previous version. ### Running the application ```bash diff --git a/README.md b/README.md index e868a1e..ab7d8da 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ curl -fsSL https://raw.githubusercontent.com/VC-HB3-Accelerator/DLE/main/setup.s Скрипт автоматически скачивает последние артефакты из релиза и разворачивает `docker-data`. ### 📦 Релизы и артефакты -- [Релиз v1.0.0](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.0) — содержит архив, разбитый на части (`dle-template.tar.gz.part-*`), который устанавливается вместе со скриптом. +- [Релиз v1.0.1](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.1) (Latest) — содержит полный шаблон приложения с Docker образами, томами и ключом шифрования. Архив разделен на части (`dle-template.tar.gz.part-*`) для удобства загрузки. +- [Релиз v1.0.0](https://github.com/VC-HB3-Accelerator/DLE/releases/tag/v1.0.0) — предыдущая версия. ### Запуск приложения ```bash diff --git a/backend/routes/russian-classifiers.js b/backend/routes/russian-classifiers.js index dc2ac00..f58af26 100644 --- a/backend/routes/russian-classifiers.js +++ b/backend/routes/russian-classifiers.js @@ -88,6 +88,24 @@ router.get('/okved', async (req, res, next) => { }); } + // Сортировка кодов ОКВЭД по коду (правильная числовая сортировка для каждой части) + codes.sort((a, b) => { + // Разбиваем коды на части для правильной сортировки + const partsA = a.code.split('.').map(p => parseInt(p, 10)); + const partsB = b.code.split('.').map(p => parseInt(p, 10)); + + // Сравниваем части по порядку численно + for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { + const partA = partsA[i] !== undefined ? partsA[i] : 0; + const partB = partsB[i] !== undefined ? partsB[i] : 0; + + if (partA !== partB) { + return partA - partB; + } + } + return 0; + }); + // Ограничиваем количество результатов для производительности const limit = parseInt(req.query.limit) || 2000; // Увеличили лимит для полного списка codes = codes.slice(0, limit); diff --git a/backend/scripts/run-migrations.js b/backend/scripts/run-migrations.js index a9bc3ee..3734720 100644 --- a/backend/scripts/run-migrations.js +++ b/backend/scripts/run-migrations.js @@ -91,7 +91,7 @@ async function runMigrations() { } // Извлекаем только UP SQL - const sqlToExecute = fileContent.substring(upSqlStartIndex, upSqlEndIndex).trim(); + let sqlToExecute = fileContent.substring(upSqlStartIndex, upSqlEndIndex).trim(); if (!sqlToExecute) { logger.warn(`Migration file ${file} has no executable UP SQL content. Skipping.`); @@ -99,6 +99,38 @@ async function runMigrations() { } logger.info(`Executing UP migration from ${file}...`); + + // Выделяем CREATE EXTENSION команды, которые должны выполняться вне транзакции + const extensionRegex = /CREATE\s+EXTENSION\s+IF\s+NOT\s+EXISTS\s+\w+;?/gi; + const extensionCommands = []; + sqlToExecute = sqlToExecute.replace(extensionRegex, (match) => { + extensionCommands.push(match.replace(/;?\s*$/, '')); + return ''; // Удаляем из основного SQL + }); + + // Выполняем CREATE EXTENSION команды вне транзакции + for (const extCmd of extensionCommands) { + try { + await pool.query(extCmd); + logger.info(`Extension command executed: ${extCmd}`); + } catch (error) { + // Игнорируем ошибку, если расширение уже установлено + if (!error.message.includes('already exists')) { + logger.warn(`Warning executing extension command: ${error.message}`); + } + } + } + + // Очищаем оставшийся SQL от пустых строк + sqlToExecute = sqlToExecute.trim(); + + // Если после удаления CREATE EXTENSION остался только пустой SQL, пропускаем транзакцию + if (!sqlToExecute) { + await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]); + logger.info(`Migration ${file} executed successfully (extension only)`); + continue; + } + await pool.query('BEGIN'); try { // Создаем функцию для получения ключа шифрования diff --git a/frontend/src/views/settings/DleDeployFormView.vue b/frontend/src/views/settings/DleDeployFormView.vue index cd57ad4..3e15ad0 100644 --- a/frontend/src/views/settings/DleDeployFormView.vue +++ b/frontend/src/views/settings/DleDeployFormView.vue @@ -1320,6 +1320,24 @@ const fetchOkvedCodes = async (level, parentCode, optionsRef, loadingRef) => { ); } + // Сортировка кодов ОКВЭД по коду (правильная числовая сортировка для каждой части) + filteredCodes.sort((a, b) => { + // Разбиваем коды на части для правильной сортировки + const partsA = a.code.split('.').map(p => parseInt(p, 10)); + const partsB = b.code.split('.').map(p => parseInt(p, 10)); + + // Сравниваем части по порядку численно + for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { + const partA = partsA[i] !== undefined ? partsA[i] : 0; + const partB = partsB[i] !== undefined ? partsB[i] : 0; + + if (partA !== partB) { + return partA - partB; + } + } + return 0; + }); + optionsRef.value = filteredCodes.map(code => ({ value: code.code, text: `${code.code} - ${code.title}` diff --git a/scripts/internal/db/db_init_helper.sh b/scripts/internal/db/db_init_helper.sh index b7b2789..cdf0177 100755 --- a/scripts/internal/db/db_init_helper.sh +++ b/scripts/internal/db/db_init_helper.sh @@ -23,12 +23,34 @@ fi ENCRYPTION_KEY=$(cat ./ssl/keys/full_db_encryption.key) # Создаем роли Read-Only и Editor +# Используем DO блок для безопасной вставки с проверкой уникальности name_encrypted docker exec dapp-postgres psql -U dapp_user -d dapp_db -c " -INSERT INTO roles (id, name_encrypted) VALUES - (1, encrypt_text('readonly', '$ENCRYPTION_KEY')), - (2, encrypt_text('editor', '$ENCRYPTION_KEY')) -ON CONFLICT (id) DO UPDATE SET - name_encrypted = EXCLUDED.name_encrypted;" +DO \$\$ +DECLARE + readonly_id INTEGER; + editor_id INTEGER; +BEGIN + -- Проверяем и создаем/обновляем роль readonly + SELECT id INTO readonly_id FROM roles WHERE decrypt_text(name_encrypted, get_encryption_key()) = 'readonly' LIMIT 1; + IF readonly_id IS NULL THEN + -- Если роли нет, пытаемся вставить с id=1, если занято - используем следующий доступный + BEGIN + INSERT INTO roles (id, name_encrypted) VALUES (1, encrypt_text('readonly', get_encryption_key())); + EXCEPTION WHEN unique_violation THEN + INSERT INTO roles (name_encrypted) VALUES (encrypt_text('readonly', get_encryption_key())); + END; + END IF; + + -- Проверяем и создаем/обновляем роль editor + SELECT id INTO editor_id FROM roles WHERE decrypt_text(name_encrypted, get_encryption_key()) = 'editor' LIMIT 1; + IF editor_id IS NULL THEN + BEGIN + INSERT INTO roles (id, name_encrypted) VALUES (2, encrypt_text('editor', get_encryption_key())); + EXCEPTION WHEN unique_violation THEN + INSERT INTO roles (name_encrypted) VALUES (encrypt_text('editor', get_encryption_key())); + END; + END IF; +END \$\$;" # Заполняем справочную таблицу is_rag_source docker exec dapp-postgres psql -U dapp_user -d dapp_db -c " @@ -38,17 +60,87 @@ INSERT INTO is_rag_source (id, name_encrypted) VALUES ON CONFLICT (id) DO UPDATE SET name_encrypted = EXCLUDED.name_encrypted;" +# Заполняем RPC провайдеры с проверкой дубликатов docker exec dapp-postgres psql -U dapp_user -d dapp_db -c " -INSERT INTO rpc_providers (network_id_encrypted, rpc_url_encrypted, chain_id) -VALUES - (encrypt_text('sepolia', '$ENCRYPTION_KEY'), encrypt_text('https://1rpc.io/sepolia', '$ENCRYPTION_KEY'), 11155111), - (encrypt_text('arbitrum-sepolia', '$ENCRYPTION_KEY'), encrypt_text('https://sepolia-rollup.arbitrum.io/rpc', '$ENCRYPTION_KEY'), 421614), - (encrypt_text('base-sepolia', '$ENCRYPTION_KEY'), encrypt_text('https://sepolia.base.org', '$ENCRYPTION_KEY'), 84532) -ON CONFLICT DO NOTHING;" +DO \$\$ +BEGIN + -- Sepolia + IF NOT EXISTS ( + SELECT 1 FROM rpc_providers + WHERE decrypt_text(network_id_encrypted, get_encryption_key()) = 'sepolia' + AND chain_id = 11155111 + ) THEN + INSERT INTO rpc_providers (network_id_encrypted, rpc_url_encrypted, chain_id) + VALUES (encrypt_text('sepolia', get_encryption_key()), encrypt_text('https://1rpc.io/sepolia', get_encryption_key()), 11155111); + END IF; + + -- Arbitrum Sepolia + IF NOT EXISTS ( + SELECT 1 FROM rpc_providers + WHERE decrypt_text(network_id_encrypted, get_encryption_key()) = 'arbitrum-sepolia' + AND chain_id = 421614 + ) THEN + INSERT INTO rpc_providers (network_id_encrypted, rpc_url_encrypted, chain_id) + VALUES (encrypt_text('arbitrum-sepolia', get_encryption_key()), encrypt_text('https://sepolia-rollup.arbitrum.io/rpc', get_encryption_key()), 421614); + END IF; + + -- Base Sepolia + IF NOT EXISTS ( + SELECT 1 FROM rpc_providers + WHERE decrypt_text(network_id_encrypted, get_encryption_key()) = 'base-sepolia' + AND chain_id = 84532 + ) THEN + INSERT INTO rpc_providers (network_id_encrypted, rpc_url_encrypted, chain_id) + VALUES (encrypt_text('base-sepolia', get_encryption_key()), encrypt_text('https://sepolia.base.org', get_encryption_key()), 84532); + END IF; +END \$\$;" + +# Заполняем токены аутентификации с проверкой дубликатов docker exec dapp-postgres psql -U dapp_user -d dapp_db -c " -INSERT INTO auth_tokens (name_encrypted, address_encrypted, network_encrypted, min_balance, readonly_threshold, editor_threshold) -VALUES - (encrypt_text('DLE', '$ENCRYPTION_KEY'), encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', '$ENCRYPTION_KEY'), encrypt_text('sepolia', '$ENCRYPTION_KEY'), 1.000000000000000000, 1, 1), - (encrypt_text('DLE', '$ENCRYPTION_KEY'), encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', '$ENCRYPTION_KEY'), encrypt_text('arbitrum-sepolia', '$ENCRYPTION_KEY'), 1.000000000000000000, 1, 1), - (encrypt_text('DLE', '$ENCRYPTION_KEY'), encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', '$ENCRYPTION_KEY'), encrypt_text('base-sepolia', '$ENCRYPTION_KEY'), 1.000000000000000000, 1, 1) -ON CONFLICT DO NOTHING;" +DO \$\$ +BEGIN + -- Sepolia token + IF NOT EXISTS ( + SELECT 1 FROM auth_tokens + WHERE decrypt_text(network_encrypted, get_encryption_key()) = 'sepolia' + AND decrypt_text(address_encrypted, get_encryption_key()) = '0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386' + ) THEN + INSERT INTO auth_tokens (name_encrypted, address_encrypted, network_encrypted, min_balance, readonly_threshold, editor_threshold) + VALUES ( + encrypt_text('DLE', get_encryption_key()), + encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', get_encryption_key()), + encrypt_text('sepolia', get_encryption_key()), + 1.000000000000000000, 1, 1 + ); + END IF; + + -- Arbitrum Sepolia token + IF NOT EXISTS ( + SELECT 1 FROM auth_tokens + WHERE decrypt_text(network_encrypted, get_encryption_key()) = 'arbitrum-sepolia' + AND decrypt_text(address_encrypted, get_encryption_key()) = '0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386' + ) THEN + INSERT INTO auth_tokens (name_encrypted, address_encrypted, network_encrypted, min_balance, readonly_threshold, editor_threshold) + VALUES ( + encrypt_text('DLE', get_encryption_key()), + encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', get_encryption_key()), + encrypt_text('arbitrum-sepolia', get_encryption_key()), + 1.000000000000000000, 1, 1 + ); + END IF; + + -- Base Sepolia token + IF NOT EXISTS ( + SELECT 1 FROM auth_tokens + WHERE decrypt_text(network_encrypted, get_encryption_key()) = 'base-sepolia' + AND decrypt_text(address_encrypted, get_encryption_key()) = '0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386' + ) THEN + INSERT INTO auth_tokens (name_encrypted, address_encrypted, network_encrypted, min_balance, readonly_threshold, editor_threshold) + VALUES ( + encrypt_text('DLE', get_encryption_key()), + encrypt_text('0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386', get_encryption_key()), + encrypt_text('base-sepolia', get_encryption_key()), + 1.000000000000000000, 1, 1 + ); + END IF; +END \$\$;" diff --git a/setup.sh b/setup.sh index 57b2600..3ac67fd 100755 --- a/setup.sh +++ b/setup.sh @@ -24,14 +24,13 @@ print_red() { echo -e "\e[31m$1\e[0m" } -ARCHIVE_VERSION="v1.0.0" +ARCHIVE_VERSION="v1.0.1" ARCHIVE_BASE_URL="https://github.com/VC-HB3-Accelerator/DLE/releases/download/${ARCHIVE_VERSION}" ARCHIVE_PARTS=( "dle-template.tar.gz.part-aa" "dle-template.tar.gz.part-ab" "dle-template.tar.gz.part-ac" "dle-template.tar.gz.part-ad" - "dle-template.tar.gz.part-ae" ) # Проверка curl @@ -263,6 +262,30 @@ import_volumes() { print_green "✅ Все тома импортированы" } +# Копирование ключа шифрования из архива +copy_encryption_key() { + print_blue "🔐 Копирование ключа шифрования..." + + # Проверяем наличие ключа в архиве + if [ -f "docker-data/ssl/keys/full_db_encryption.key" ]; then + # Создаем директорию для ключа + mkdir -p ssl/keys + + # Копируем ключ + cp docker-data/ssl/keys/full_db_encryption.key ssl/keys/full_db_encryption.key + chmod 600 ssl/keys/full_db_encryption.key + + print_green "✅ Ключ шифрования скопирован из архива" + print_yellow "⚠️ Примечание: Это дефолтный ключ, замените его на свой!" + else + print_yellow "⚠️ Ключ шифрования не найден в архиве" + print_blue "Создайте новый ключ или он будет создан автоматически" + + # Создаем директорию для ключа на всякий случай + mkdir -p ssl/keys + fi +} + # Запуск приложения start_application() { print_blue "🚀 Запуск приложения..." @@ -314,6 +337,9 @@ main() { create_volumes import_volumes + # Копирование ключа шифрования + copy_encryption_key + # Запуск start_application check_status