'use strict'; const db = require('./src/db/db'); const bcrypt = require('bcryptjs'); /* ─────────────────────────── helpers ────────────────────────────────── */ function daysAgo(d, h = 0, m = 0) { const dt = new Date(); dt.setDate(dt.getDate() - d); dt.setHours(dt.getHours() - h, dt.getMinutes() - m, 0, 0); return dt.toISOString().replace('T', ' ').slice(0, 19); } function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; } function shuffle(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } function rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } /* ─────────────────────────── data ───────────────────────────────────── */ const PASS = bcrypt.hashSync('pass1234', 10); const TEACHER = { name: 'Наталья Смирнова', email: 'teacher@school.by', role: 'teacher' }; const STUDENTS = [ { name: 'Алексей Петров', email: 'petrov@school.by', skill: 0.85 }, { name: 'Мария Иванова', email: 'ivanova@school.by', skill: 0.78 }, { name: 'Дмитрий Соколов', email: 'sokolov@school.by', skill: 0.62 }, { name: 'Анна Козлова', email: 'kozlova@school.by', skill: 0.91 }, { name: 'Иван Новиков', email: 'novikov@school.by', skill: 0.55 }, { name: 'Екатерина Морозова', email: 'morozova@school.by', skill: 0.73 }, { name: 'Сергей Волков', email: 'volkov@school.by', skill: 0.40 }, { name: 'Ольга Лебедева', email: 'lebedeva@school.by', skill: 0.82 }, { name: 'Никита Зайцев', email: 'zajcev@school.by', skill: 0.48 }, { name: 'Виктория Семёнова', email: 'semenova@school.by', skill: 0.69 }, { name: 'Артём Павлов', email: 'pavlov@school.by', skill: 0.30, lazy: true }, { name: 'Елена Фёдорова', email: 'fedorova@school.by', skill: 0.95 }, { name: 'Максим Орлов', email: 'orlov@school.by', skill: 0.58 }, { name: 'Юлия Попова', email: 'popova@school.by', skill: 0.76, lazy: true }, ]; /* ─────────────────────────── insert users ───────────────────────────── */ const insertUser = db.prepare( 'INSERT OR IGNORE INTO users (email, password_hash, name, role) VALUES (?, ?, ?, ?)' ); const getUser = db.prepare('SELECT id FROM users WHERE email = ?'); insertUser.run(TEACHER.email, PASS, TEACHER.name, TEACHER.role); const teacherId = getUser.get(TEACHER.email).id; console.log(`✓ Teacher: ${TEACHER.name} (id=${teacherId})`); const studentIds = []; for (const s of STUDENTS) { insertUser.run(s.email, PASS, s.name, 'student'); const id = getUser.get(s.email).id; studentIds.push({ id, skill: s.skill, lazy: !!s.lazy, name: s.name }); } console.log(`✓ ${STUDENTS.length} students inserted`); /* ─────────────────────────── classes ────────────────────────────────── */ function genCode() { return Math.random().toString(36).slice(2, 9).toUpperCase(); } const insertClass = db.prepare( 'INSERT OR IGNORE INTO classes (name, description, teacher_id, invite_code) VALUES (?, ?, ?, ?)' ); const getClassByName = db.prepare('SELECT id FROM classes WHERE name = ? AND teacher_id = ?'); const classData = [ { name: '11А · Биология', desc: 'Подготовка к ЦТ 2026 по биологии', subject: 'bio', subjectId: 1 }, { name: '10Б · Математика', desc: 'Алгебра и начала анализа', subject: 'math', subjectId: 3 }, ]; const classes = []; for (const c of classData) { insertClass.run(c.name, c.desc, teacherId, genCode()); const row = getClassByName.get(c.name, teacherId); classes.push({ ...c, id: row.id }); console.log(`✓ Class: "${c.name}" (id=${row.id})`); } /* ─────────────────────────── enroll students ────────────────────────── */ const insertMember = db.prepare( 'INSERT OR IGNORE INTO class_members (class_id, user_id, joined_at) VALUES (?, ?, ?)' ); // Класс 1 (bio) — все студенты for (const s of studentIds) { insertMember.run(classes[0].id, s.id, daysAgo(rand(20, 40))); } // Класс 2 (math) — первые 10 студентов for (const s of studentIds.slice(0, 10)) { insertMember.run(classes[1].id, s.id, daysAgo(rand(15, 35))); } console.log('✓ Students enrolled in classes'); /* ─────────────────────────── load questions ─────────────────────────── */ const allQuestions = db.prepare( 'SELECT id, subject_id, type FROM questions WHERE type = ? OR type IS NULL' ).all('single'); const questionsBySubject = {}; for (const q of allQuestions) { if (!questionsBySubject[q.subject_id]) questionsBySubject[q.subject_id] = []; questionsBySubject[q.subject_id].push(q); } // Load all options grouped by question const allOptions = db.prepare('SELECT id, question_id, is_correct FROM options').all(); const optionsByQuestion = {}; for (const o of allOptions) { if (!optionsByQuestion[o.question_id]) optionsByQuestion[o.question_id] = []; optionsByQuestion[o.question_id].push(o); } /* ─────────────────────────── simulate session ────────────────────────── */ const insertSession = db.prepare( "INSERT INTO test_sessions (user_id, subject_id, mode, total, score, status, started_at, finished_at) VALUES (?, ?, ?, ?, ?, 'completed', ?, ?)" ); const insertSQ = db.prepare( 'INSERT OR IGNORE INTO session_questions (session_id, question_id, order_index) VALUES (?, ?, ?)' ); const insertAnswer = db.prepare( 'INSERT OR IGNORE INTO user_answers (session_id, question_id, chosen_option_id, is_correct, time_spent_sec, answered_at) VALUES (?, ?, ?, ?, ?, ?)' ); const insertAssignSes = db.prepare( 'INSERT OR IGNORE INTO assignment_sessions (assignment_id, user_id, session_id) VALUES (?, ?, ?)' ); function simulateSession(userId, subjectId, mode, count, skill, daysAgoN) { const pool = shuffle(questionsBySubject[subjectId] || []).slice(0, count); if (!pool.length) return null; const startedAt = daysAgo(daysAgoN, rand(0, 6)); const finishedAt = daysAgo(daysAgoN, rand(0, 5), rand(5, 40)); let score = 0; const info = insertSession.run(userId, subjectId, mode, pool.length, 0, startedAt, finishedAt); const sessionId = info.lastInsertRowid; pool.forEach((q, idx) => { insertSQ.run(sessionId, q.id, idx); const opts = optionsByQuestion[q.id] || []; if (!opts.length) return; const correct = opts.find(o => o.is_correct); const wrong = opts.filter(o => !o.is_correct); // skill = probability of answering correctly const isCorrect = Math.random() < skill; let chosen; if (isCorrect && correct) { chosen = correct; } else { chosen = wrong.length ? pick(wrong) : pick(opts); } const wasCorrect = chosen.is_correct ? 1 : 0; if (wasCorrect) score++; insertAnswer.run(sessionId, q.id, chosen.id, wasCorrect, rand(8, 95), finishedAt); }); // Update score db.prepare('UPDATE test_sessions SET score = ? WHERE id = ?').run(score, sessionId); return sessionId; } /* ─────────────────────────── assignments ────────────────────────────── */ const insertAssign = db.prepare(` INSERT OR IGNORE INTO assignments (class_id, title, subject_slug, mode, count, deadline, created_by, created_at) VALUES (?, ?, ?, 'exam', ?, ?, ?, ?) `); const getAssign = db.prepare('SELECT id FROM assignments WHERE class_id = ? AND title = ?'); const assignmentDefs = [ // Bio class { classIdx: 0, title: 'Клетка и её строение', subjectId: 1, count: 15, createdAgo: 28, deadlineAgo: 21, studentSkipChance: 0.0 }, { classIdx: 0, title: 'Генетика — базовый уровень', subjectId: 1, count: 20, createdAgo: 18, deadlineAgo: 11, studentSkipChance: 0.05 }, { classIdx: 0, title: 'Контрольная: Митоз и мейоз', subjectId: 1, count: 25, createdAgo: 10, deadlineAgo: 5, studentSkipChance: 0.10 }, { classIdx: 0, title: 'Фотосинтез и дыхание', subjectId: 1, count: 15, createdAgo: 3, deadlineAgo: null, studentSkipChance: 0.20 }, // Math class { classIdx: 1, title: 'Квадратные уравнения', subjectId: 3, count: 10, createdAgo: 22, deadlineAgo: 16, studentSkipChance: 0.0 }, { classIdx: 1, title: 'Тригонометрия — тест', subjectId: 3, count: 15, createdAgo: 12, deadlineAgo: 7, studentSkipChance: 0.08 }, { classIdx: 1, title: 'Логарифмы и степени', subjectId: 3, count: 15, createdAgo: 4, deadlineAgo: null, studentSkipChance: 0.25 }, ]; for (const a of assignmentDefs) { const cls = classes[a.classIdx]; const deadline = a.deadlineAgo ? daysAgo(a.deadlineAgo) : null; insertAssign.run(cls.id, a.title, cls.subject, a.count, deadline, teacherId, daysAgo(a.createdAgo)); const row = getAssign.get(cls.id, a.title); if (!row) { console.warn(` ! Could not get assignment id for "${a.title}"`); continue; } const assignId = row.id; // Determine enrolled students for this class const enrolled = a.classIdx === 0 ? studentIds : studentIds.slice(0, 10); let done = 0; for (const s of enrolled) { // Lazy students skip recent assignments if (s.lazy && a.studentSkipChance > 0.15) continue; // Random skip by skip chance if (Math.random() < a.studentSkipChance) continue; const sessionId = simulateSession( s.id, a.subjectId, 'exam', a.count, s.skill, rand(a.deadlineAgo ? a.deadlineAgo : 1, a.createdAgo) ); if (sessionId) { insertAssignSes.run(assignId, s.id, sessionId); done++; } } console.log(`✓ Assignment "${a.title}" — ${done}/${enrolled.length} completed`); } /* ─────────────────────────── free-form sessions ─────────────────────── */ // Some students did extra practice on their own const practiceSubjects = [[1, 'bio'], [3, 'math']]; for (const s of studentIds) { if (s.lazy) continue; const extraCount = rand(1, 4); for (let i = 0; i < extraCount; i++) { const [subId] = pick(practiceSubjects); simulateSession(s.id, subId, 'practice', rand(10, 25), s.skill + 0.05, rand(1, 30)); } } console.log('✓ Extra practice sessions simulated'); /* ─────────────────────────── announcements ──────────────────────────── */ const insertAnn = db.prepare( 'INSERT INTO announcements (class_id, author_id, text, created_at) VALUES (?, ?, ?, ?)' ); const annTexts = [ [0, '📅 Напоминаю: контрольная по митозу пройдёт в следующую пятницу. Повторите темы деления клетки.', 9], [0, '✅ Результаты по теме «Клетка» проверены. Молодцы! Средний балл — 74%. Слабее всего — органоиды.', 20], [0, '📚 Для подготовки к ЦТ рекомендую дополнительно пройти тест по фотосинтезу в системе.', 5], [1, '🧮 Разбор ошибок по квадратным уравнениям будет в среду, 14:00, кабинет 204.', 11], [1, '⚠️ Дедлайн по логарифмам — в пятницу. Кто не сдал тригонометрию — сдайте до конца недели.', 3], ]; for (const [classIdx, text, ago] of annTexts) { insertAnn.run(classes[classIdx].id, teacherId, text, daysAgo(ago)); } console.log('✓ Announcements created'); /* ─────────────────────────── notifications ──────────────────────────── */ const insertNotif = db.prepare( 'INSERT INTO notifications (user_id, type, message, link, is_read, created_at) VALUES (?, ?, ?, ?, ?, ?)' ); // Teacher gets notifs about students completing assignments for (const s of studentIds.slice(0, 6)) { insertNotif.run(teacherId, 'session', `«${s.name}» сдал «Генетика — базовый уровень» — ${rand(55, 95)}%`, '/classes.html', 1, daysAgo(rand(5, 15))); } // Students get assignment notifs for (const s of studentIds) { insertNotif.run(s.id, 'assignment', '📋 Для вас задание: «Контрольная: Митоз и мейоз»', '/dashboard.html', 1, daysAgo(10)); insertNotif.run(s.id, 'assignment', '📋 Для вас задание: «Фотосинтез и дыхание»', '/dashboard.html', rand(0,1), daysAgo(3)); } console.log('✓ Notifications created'); /* ─────────────────────────── summary ────────────────────────────────── */ const stats = { users: db.prepare('SELECT COUNT(*) as n FROM users').get().n, sessions: db.prepare('SELECT COUNT(*) as n FROM test_sessions').get().n, answers: db.prepare('SELECT COUNT(*) as n FROM user_answers').get().n, assigns: db.prepare('SELECT COUNT(*) as n FROM assignments').get().n, }; console.log('\n═══ Seed complete ═══'); console.log(` Users: ${stats.users}`); console.log(` Sessions: ${stats.sessions}`); console.log(` Answers: ${stats.answers}`); console.log(` Assignments: ${stats.assigns}`); console.log('\n Password for all accounts: pass1234'); console.log(` Teacher: ${TEACHER.email}`); console.log(` Students: ${STUDENTS[0].email} … ${STUDENTS[STUDENTS.length-1].email}`);