%
This commit is contained in:
483
migrate.js
Normal file
483
migrate.js
Normal 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);
|
||||
Reference in New Issue
Block a user