This commit is contained in:
2025-12-21 20:42:12 +05:00
parent 04db1718aa
commit a5c983c6c6
16 changed files with 5880 additions and 2836 deletions

483
migrate.js Normal file
View File

@@ -0,0 +1,483 @@
const sqlite3 = require('sqlite3').verbose();
const { Pool } = require('pg');
const path = require('path');
const fs = require('fs');
require('dotenv').config();
async function migrateToPostgres() {
console.log('🚀 Начинаем миграцию данных из SQLite в PostgreSQL...');
// Проверяем существование SQLite базы
const sqlitePath = path.join(__dirname, 'data', 'school_crm.db');
if (!fs.existsSync(sqlitePath)) {
console.error('❌ Файл SQLite базы не найден:', sqlitePath);
process.exit(1);
}
// Подключаемся к SQLite
const sqliteDb = new sqlite3.Database(sqlitePath, (err) => {
if (err) {
console.error('❌ Ошибка подключения к SQLite:', err.message);
process.exit(1);
}
});
console.log('✅ SQLite база найдена и подключена');
// Проверяем настройки PostgreSQL
if (!process.env.DB_HOST || !process.env.DB_USER || !process.env.DB_PASSWORD) {
console.error('❌ Настройки PostgreSQL не указаны в .env файле');
console.error(' Укажите DB_HOST, DB_USER, DB_PASSWORD');
process.exit(1);
}
// Подключаемся к PostgreSQL
const pgPool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'minicrm',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 5,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
let client;
try {
console.log('🔌 Подключаемся к PostgreSQL...');
client = await pgPool.connect();
console.log('✅ Подключение к PostgreSQL установлено');
// Создаем таблицы в PostgreSQL если их нет
console.log('🔧 Создаем/проверяем таблицы в PostgreSQL...');
await createPostgresTables(client);
// Отключаем foreign key constraints для упрощения миграции
await client.query('SET session_replication_role = replica;');
// Мигрируем таблицу users
console.log('📦 Мигрируем таблицу users...');
const users = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM users ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (users.length > 0) {
let migratedUsers = 0;
for (const user of users) {
try {
await client.query(`
INSERT INTO users (id, login, password, name, email, role, auth_type,
groups, description, created_at, last_login, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT (id) DO UPDATE SET
login = EXCLUDED.login,
name = EXCLUDED.name,
email = EXCLUDED.email,
role = EXCLUDED.role,
auth_type = EXCLUDED.auth_type,
groups = EXCLUDED.groups,
description = EXCLUDED.description,
last_login = EXCLUDED.last_login,
updated_at = EXCLUDED.updated_at
`, [
user.id,
user.login,
user.password || null,
user.name,
user.email,
user.role || 'teacher',
user.auth_type || 'local',
user.groups || '[]',
user.description || '',
user.created_at,
user.last_login,
user.updated_at || user.created_at
]);
migratedUsers++;
} catch (error) {
console.error(` Ошибка при миграции пользователя ${user.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedUsers} из ${users.length} пользователей`);
} else {
console.log(' В таблице users нет данных для миграции');
}
// Мигрируем таблицу tasks
console.log('📦 Мигрируем таблицу tasks...');
const tasks = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM tasks ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (tasks.length > 0) {
let migratedTasks = 0;
for (const task of tasks) {
try {
await client.query(`
INSERT INTO tasks (id, title, description, status, created_by, created_at,
updated_at, deleted_at, deleted_by, original_task_id,
start_date, due_date, rework_comment, closed_at, closed_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
ON CONFLICT (id) DO UPDATE SET
title = EXCLUDED.title,
description = EXCLUDED.description,
status = EXCLUDED.status,
created_by = EXCLUDED.created_by,
updated_at = EXCLUDED.updated_at,
deleted_at = EXCLUDED.deleted_at,
deleted_by = EXCLUDED.deleted_by,
original_task_id = EXCLUDED.original_task_id,
start_date = EXCLUDED.start_date,
due_date = EXCLUDED.due_date,
rework_comment = EXCLUDED.rework_comment,
closed_at = EXCLUDED.closed_at,
closed_by = EXCLUDED.closed_by
`, [
task.id,
task.title,
task.description || '',
task.status || 'active',
task.created_by,
task.created_at,
task.updated_at || task.created_at,
task.deleted_at,
task.deleted_by,
task.original_task_id,
task.start_date,
task.due_date,
task.rework_comment,
task.closed_at,
task.closed_by
]);
migratedTasks++;
} catch (error) {
console.error(` Ошибка при миграции задачи ${task.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedTasks} из ${tasks.length} задач`);
} else {
console.log(' В таблице tasks нет данных для миграции');
}
// Мигрируем таблицу task_assignments
console.log('📦 Мигрируем таблицу task_assignments...');
const assignments = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM task_assignments ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (assignments.length > 0) {
let migratedAssignments = 0;
for (const assignment of assignments) {
try {
await client.query(`
INSERT INTO task_assignments (id, task_id, user_id, status, start_date,
due_date, rework_comment, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (id) DO UPDATE SET
task_id = EXCLUDED.task_id,
user_id = EXCLUDED.user_id,
status = EXCLUDED.status,
start_date = EXCLUDED.start_date,
due_date = EXCLUDED.due_date,
rework_comment = EXCLUDED.rework_comment,
updated_at = EXCLUDED.updated_at
`, [
assignment.id,
assignment.task_id,
assignment.user_id,
assignment.status || 'assigned',
assignment.start_date,
assignment.due_date,
assignment.rework_comment,
assignment.created_at,
assignment.updated_at || assignment.created_at
]);
migratedAssignments++;
} catch (error) {
console.error(` Ошибка при миграции назначения ${assignment.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedAssignments} из ${assignments.length} назначений`);
} else {
console.log(' В таблице task_assignments нет данных для миграции');
}
// Мигрируем таблицу task_files
console.log('📦 Мигрируем таблицу task_files...');
const files = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM task_files ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (files.length > 0) {
let migratedFiles = 0;
for (const file of files) {
try {
await client.query(`
INSERT INTO task_files (id, task_id, user_id, filename, original_name,
file_path, file_size, uploaded_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (id) DO UPDATE SET
task_id = EXCLUDED.task_id,
user_id = EXCLUDED.user_id,
filename = EXCLUDED.filename,
original_name = EXCLUDED.original_name,
file_path = EXCLUDED.file_path,
file_size = EXCLUDED.file_size
`, [
file.id,
file.task_id,
file.user_id,
file.filename,
file.original_name,
file.file_path,
file.file_size,
file.uploaded_at
]);
migratedFiles++;
} catch (error) {
console.error(` Ошибка при миграции файла ${file.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedFiles} из ${files.length} файлов`);
} else {
console.log(' В таблице task_files нет данных для миграции');
}
// Мигрируем таблицу activity_logs
console.log('📦 Мигрируем таблицу activity_logs...');
const logs = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM activity_logs ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (logs.length > 0) {
let migratedLogs = 0;
for (const log of logs) {
try {
await client.query(`
INSERT INTO activity_logs (id, task_id, user_id, action, details, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE SET
task_id = EXCLUDED.task_id,
user_id = EXCLUDED.user_id,
action = EXCLUDED.action,
details = EXCLUDED.details
`, [
log.id,
log.task_id,
log.user_id,
log.action,
log.details || '',
log.created_at
]);
migratedLogs++;
} catch (error) {
console.error(` Ошибка при миграции лога ${log.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedLogs} из ${logs.length} логов активности`);
} else {
console.log(' В таблице activity_logs нет данных для миграции');
}
// Мигрируем таблицу notification_logs
console.log('📦 Мигрируем таблицу notification_logs...');
const notifications = await new Promise((resolve, reject) => {
sqliteDb.all('SELECT * FROM notification_logs ORDER BY id', [], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
if (notifications.length > 0) {
let migratedNotifications = 0;
for (const notification of notifications) {
try {
await client.query(`
INSERT INTO notification_logs (id, notification_key, created_at)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO UPDATE SET
notification_key = EXCLUDED.notification_key
`, [
notification.id,
notification.notification_key,
notification.created_at
]);
migratedNotifications++;
} catch (error) {
console.error(` Ошибка при миграции уведомления ${notification.id}:`, error.message);
}
}
console.log(`✅ Мигрировано ${migratedNotifications} из ${notifications.length} логов уведомлений`);
} else {
console.log(' В таблице notification_logs нет данных для миграции');
}
// Включаем foreign key constraints обратно
await client.query('SET session_replication_role = DEFAULT;');
// Обновляем последовательности
await updateSequences(client);
client.release();
sqliteDb.close();
console.log('\n🎉 Миграция успешно завершена!');
console.log('📊 Сводка:');
console.log(` 👥 Пользователи: ${users.length}`);
console.log(` 📋 Задачи: ${tasks.length}`);
console.log(` 👤 Назначения: ${assignments.length}`);
console.log(` 📁 Файлы: ${files.length}`);
console.log(` 📝 Логи активности: ${logs.length}`);
console.log(` 🔔 Логи уведомлений: ${notifications.length}`);
console.log('\n⚠ Для переключения на PostgreSQL выполните следующие действия:');
console.log(' 1. Откройте файл .env');
console.log(' 2. Измените POSTGRESQL=no на POSTGRESQL=yes');
console.log(' 3. Перезапустите сервер командой: npm start');
} catch (error) {
console.error('❌ Ошибка миграции:', error.message);
if (client) client.release();
sqliteDb.close();
process.exit(1);
} finally {
await pgPool.end();
}
}
async function createPostgresTables(client) {
// Создаем таблицы PostgreSQL
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
login VARCHAR(100) UNIQUE NOT NULL,
password TEXT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
role VARCHAR(50) DEFAULT 'teacher',
auth_type VARCHAR(50) DEFAULT 'local',
groups TEXT DEFAULT '[]',
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await client.query(`
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(50) DEFAULT 'active',
created_by INTEGER NOT NULL REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP,
deleted_by INTEGER REFERENCES users(id),
original_task_id INTEGER REFERENCES tasks(id),
start_date TIMESTAMP,
due_date TIMESTAMP,
rework_comment TEXT,
closed_at TIMESTAMP,
closed_by INTEGER REFERENCES users(id)
)
`);
await client.query(`
CREATE TABLE IF NOT EXISTS task_assignments (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
status VARCHAR(50) DEFAULT 'assigned',
start_date TIMESTAMP,
due_date TIMESTAMP,
rework_comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await client.query(`
CREATE TABLE IF NOT EXISTS task_files (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
filename VARCHAR(255) NOT NULL,
original_name VARCHAR(500) NOT NULL,
file_path TEXT NOT NULL,
file_size BIGINT NOT NULL,
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await client.query(`
CREATE TABLE IF NOT EXISTS activity_logs (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
action VARCHAR(100) NOT NULL,
details TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await client.query(`
CREATE TABLE IF NOT EXISTS notification_logs (
id SERIAL PRIMARY KEY,
notification_key VARCHAR(500) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('✅ Таблицы PostgreSQL созданы/проверены');
}
async function updateSequences(client) {
// Обновляем последовательности для автоинкремента
const tables = [
{ name: 'users', id: 'id' },
{ name: 'tasks', id: 'id' },
{ name: 'task_assignments', id: 'id' },
{ name: 'task_files', id: 'id' },
{ name: 'activity_logs', id: 'id' },
{ name: 'notification_logs', id: 'id' }
];
for (const table of tables) {
try {
const result = await client.query(`
SELECT MAX(${table.id}) as max_id FROM ${table.name}
`);
const maxId = result.rows[0].max_id || 0;
if (maxId > 0) {
await client.query(`
SELECT setval(pg_get_serial_sequence('${table.name}', '${table.id}'), ${maxId}, true)
`);
console.log(`🔢 Последовательность для ${table.name} обновлена до ${maxId}`);
}
} catch (error) {
console.warn(`⚠️ Не удалось обновить последовательность для ${table.name}: ${error.message}`);
}
}
}
// Запускаем миграцию
migrateToPostgres().catch(console.error);