diff --git a/.env.production.example b/.env.production.example index 632998f..fd91b6d 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,8 +1,5 @@ # Production environment variables for KSPGUTI Schedule -# Database directory (where schedule-app.db will be stored) -DATABASE_DIR=/opt/kspguti-schedule - # Site URL - used for canonical links and sitemap (optional, defaults to https://schedule.itlxrd.space) NEXT_PUBLIC_SITE_URL=https://schedule.itlxrd.space diff --git a/scripts/test-teachers-db.js b/scripts/test-teachers-db.js deleted file mode 100644 index 889e706..0000000 --- a/scripts/test-teachers-db.js +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env node - -/** - * Скрипт для проверки базы данных преподавателей - * Запуск: node scripts/test-teachers-db.js - */ - -const path = require('path'); -const fs = require('fs'); - -// Определяем директорию базы данных -function getDatabaseDir() { - if (process.env.DATABASE_DIR) { - return process.env.DATABASE_DIR; - } - - const cwd = process.cwd(); - console.log(`Current working directory: ${cwd}`); - - if (cwd.includes('.next/standalone')) { - const standaloneMatch = cwd.match(/^(.+?)\/\.next\/standalone/); - if (standaloneMatch && standaloneMatch[1]) { - return standaloneMatch[1]; - } - return path.resolve(cwd, '..', '..'); - } - - if (fs.existsSync('/opt/kspguti-schedule')) { - return '/opt/kspguti-schedule'; - } - - return cwd; -} - -const DATABASE_DIR = getDatabaseDir(); -const DB_PATH = path.join(DATABASE_DIR, 'db', 'schedule-app.db'); - -console.log(`Database directory: ${DATABASE_DIR}`); -console.log(`Database path: ${DB_PATH}`); -console.log(`Database exists: ${fs.existsSync(DB_PATH)}`); - -if (!fs.existsSync(DB_PATH)) { - console.error('Database file does not exist!'); - process.exit(1); -} - -// Проверяем права доступа -try { - fs.accessSync(DB_PATH, fs.constants.R_OK | fs.constants.W_OK); - console.log('Database file is readable and writable'); -} catch (err) { - console.error('Database file permissions error:', err.message); - process.exit(1); -} - -// Подключаемся к базе данных -const Database = require('better-sqlite3'); -const db = new Database(DB_PATH); - -// Проверяем таблицу teachers -console.log('\n=== Teachers Table ==='); -const teachersCount = db.prepare('SELECT COUNT(*) as count FROM teachers').get(); -console.log(`Total teachers in database: ${teachersCount.count}`); - -if (teachersCount.count > 0) { - const teachers = db.prepare('SELECT id, parseId, name FROM teachers LIMIT 10').all(); - console.log('First 10 teachers:'); - teachers.forEach((t, i) => { - console.log(` ${i + 1}. [${t.id}] ${t.name} (parseId: ${t.parseId})`); - }); -} else { - console.log('Teachers table is EMPTY!'); -} - -// Проверяем таблицу groups -console.log('\n=== Groups Table ==='); -const groupsCount = db.prepare('SELECT COUNT(*) as count FROM groups').get(); -console.log(`Total groups in database: ${groupsCount.count}`); - -if (groupsCount.count > 0) { - const groups = db.prepare('SELECT id, parseId, name, course FROM groups LIMIT 10').all(); - console.log('First 10 groups:'); - groups.forEach((g, i) => { - console.log(` ${i + 1}. [${g.id}] ${g.name} (parseId: ${g.parseId}, course: ${g.course})`); - }); -} - -// Проверяем таблицу settings -console.log('\n=== Settings Table ==='); -const settings = db.prepare('SELECT value FROM settings WHERE key = ?').get('app'); -if (settings) { - console.log('App settings:', settings.value); -} else { - console.log('No app settings found'); -} - -db.close(); -console.log('\nDone!'); diff --git a/scripts/test-teachers-parser.js b/scripts/test-teachers-parser.js deleted file mode 100644 index 2fa86c0..0000000 --- a/scripts/test-teachers-parser.js +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env node - -/** - * Тест парсера преподавателей - * Запуск: node scripts/test-teachers-parser.js - */ - -const { JSDOM } = require('jsdom'); -const path = require('path'); -const fs = require('fs'); - -// Импортируем парсер -const { parseTeachersList } = require('./src/app/parser/teachers-list'); - -// HTML с сервера (сохраните в файл или передайте через аргумент) -const testHtml = ` - - - - - - - - -
Абалымова Людмила Павловна
Абрамова Светлана Геннадьевна
Айриянц Илона Артуровна
Алёхин Иван Николаевич
Андреевская Наталья Владимировна
- - -`; - -console.log('=== Testing Teachers Parser ===\n'); - -// Создаём JSDOM -const dom = new JSDOM(testHtml, { url: 'https://lk.ks.psuti.ru/?mn=3' }); -const document = dom.window.document; - -// Проверяем, находит ли селектор ссылки -const links = Array.from(document.querySelectorAll('a[href*="?mn=3&obj="], a[href*="mn=3&obj="]')); -console.log(`Links found by selector: ${links.length}`); -links.forEach((link, i) => { - console.log(` ${i + 1}. href="${link.getAttribute('href')}", text="${link.textContent?.trim()}"`); -}); - -// Запускаем парсер -const teachers = parseTeachersList(document); -console.log(`\nTeachers parsed: ${teachers.length}`); -teachers.forEach((t, i) => { - console.log(` ${i + 1}. [${t.parseId}] ${t.name}`); -}); - -dom.window.close(); - -// Теперь тестируем на реальном HTML с сервера -console.log('\n\n=== Testing with Real HTML from Server ===\n'); - -const realHtmlPath = path.join(__dirname, 'teachers-test.html'); -if (fs.existsSync(realHtmlPath)) { - const realHtml = fs.readFileSync(realHtmlPath, 'utf8'); - const realDom = new JSDOM(realHtml, { url: 'https://lk.ks.psuti.ru/?mn=3' }); - const realDocument = realDom.window.document; - - const realTeachers = parseTeachersList(realDocument); - console.log(`Real teachers parsed: ${realTeachers.length}`); - realTeachers.slice(0, 10).forEach((t, i) => { - console.log(` ${i + 1}. [${t.parseId}] ${t.name}`); - }); - - if (realTeachers.length > 10) { - console.log(` ... and ${realTeachers.length - 10} more`); - } - - realDom.window.close(); -} else { - console.log(`Test file not found: ${realHtmlPath}`); - console.log('To test with real HTML, save the curl output to scripts/teachers-test.html'); - console.log('Example: curl -L "https://lk.ks.psuti.ru/?mn=3" > scripts/teachers-test.html'); -} diff --git a/src/app/parser/schedule.ts b/src/app/parser/schedule.ts index 144bdcd..56d2a6c 100644 --- a/src/app/parser/schedule.ts +++ b/src/app/parser/schedule.ts @@ -421,8 +421,7 @@ function parseTeacherSchedule( currentWeekNumber = weekNumber } - // Ищем родительскую таблицу с парами - // Сначала пробуем найти по cellpadding="1" + // Ищем родительскую таблицу с парами (cellpadding="1") let parent: Element | null = anchor as Element for (let i = 0; i < 10 && parent; i++) { parent = parent.parentElement @@ -431,24 +430,6 @@ function parseTeacherSchedule( } } - // Если не нашли по cellpadding, ищем просто ближайшую таблицу - if (!parent || parent.tagName !== 'TABLE') { - parent = anchor.closest('table') - } - - // Если все еще не нашли, ищем таблицу рядом с якорем - if (!parent || parent.tagName !== 'TABLE') { - // Ищем следующую таблицу после якоря - let nextSibling: Node | null = anchor as Node - while (nextSibling) { - nextSibling = nextSibling.nextSibling - if (nextSibling && nextSibling.nodeType === 1 && (nextSibling as Element).tagName === 'TABLE') { - parent = nextSibling as Element - break - } - } - } - const lessons: Lesson[] = [] if (parent && parent.tagName === 'TABLE') { @@ -467,8 +448,7 @@ function parseTeacherSchedule( const endTime = (endTimeRaw || '').trim() const subjCell = cells[2] - // Проверяем наличие ячейки перед доступом к textContent - const roomText = cells[3]?.textContent?.trim() || '' + const roomText = cells[3].textContent?.trim() || '' // Извлекаем предмет, аудиторию и тип занятия по логике python‑парсера let subject = '' @@ -477,20 +457,19 @@ function parseTeacherSchedule( let lessonType = '' let location = '' - // Проверяем наличие subjCell перед поиском элементов - const bold = subjCell?.querySelector('b') + const bold = subjCell.querySelector('b') if (bold) { subject = bold.textContent?.trim() || '' } - const fontGreen = subjCell?.querySelector('font.t_green_10') + const fontGreen = subjCell.querySelector('font.t_green_10') if (fontGreen) { location = fontGreen.textContent?.trim() || '' } // Всё, что идёт после до , это строка с группой и типом занятия let raw = '' - if (bold && subjCell) { + if (bold) { let node: ChildNode | null = bold.nextSibling while (node) { const nodeType = (node as any).nodeType @@ -1069,24 +1048,7 @@ export function parsePage( // Для расписания преподавателей используем отдельный, более надежный парсер, // основанный на уже отлаженной python‑версии. if (isTeacherSchedule) { - try { - const result = parseTeacherSchedule(document, url, shouldParseWeekNavigation) - // Если парсер не нашел дней, пробуем fallback на parseGroupSchedule - if (result.days.length === 0) { - logDebug('parsePage: parseTeacherSchedule returned no days, trying fallback') - return parseGroupSchedule(document, groupName, url, shouldParseWeekNavigation) - } - return result - } catch (error) { - // При ошибке парсинга преподавателя, пробуем fallback - logDebug('parsePage: parseTeacherSchedule failed, trying fallback', { error }) - try { - return parseGroupSchedule(document, groupName, url, shouldParseWeekNavigation) - } catch (fallbackError) { - // Если и fallback не сработал, выбрасываем оригинальную ошибку - throw error - } - } + return parseTeacherSchedule(document, url, shouldParseWeekNavigation) } // Для расписания групп используем отдельный парсер, который опирается на структуру diff --git a/src/app/parser/teachers-list.ts b/src/app/parser/teachers-list.ts index b03c922..f51928e 100644 --- a/src/app/parser/teachers-list.ts +++ b/src/app/parser/teachers-list.ts @@ -13,32 +13,32 @@ export type TeacherListItem = { */ export function parseTeachersList(document: Document): TeacherListItem[] { const teachers: TeacherListItem[] = [] - - // Способ 1: Ищем все ссылки, которые содержат ?mn=3&obj= или mn=3&obj= + + // Ищем все ссылки, которые содержат ?mn=3&obj= const links = Array.from(document.querySelectorAll('a[href*="?mn=3&obj="], a[href*="mn=3&obj="]')) - + for (const link of links) { const href = link.getAttribute('href') if (!href) continue - + // Парсим URL вида ?mn=3&obj=XXX или /?mn=3&obj=XXX const objMatch = href.match(/[?&]obj=(\d+)/) if (!objMatch) continue - + const parseId = Number(objMatch[1]) if (isNaN(parseId) || parseId <= 0) continue - + // Извлекаем имя преподавателя из текста ссылки const name = link.textContent?.trim() if (!name || name.length === 0) continue - + // Проверяем, что это не дубликат if (!teachers.find(t => t.parseId === parseId)) { teachers.push({ parseId, name }) } } - - // Способ 2: Если не нашли ссылки, пытаемся найти в таблице + + // Если не нашли ссылки, пытаемся найти в таблице if (teachers.length === 0) { const tables = Array.from(document.querySelectorAll('table')) for (const table of tables) { @@ -46,71 +46,28 @@ export function parseTeachersList(document: Document): TeacherListItem[] { for (const row of rows) { const link = row.querySelector('a[href*="obj="]') if (!link) continue - + const href = link.getAttribute('href') if (!href || !href.includes('mn=3')) continue - + const objMatch = href.match(/[?&]obj=(\d+)/) if (!objMatch) continue - + const parseId = Number(objMatch[1]) if (isNaN(parseId) || parseId <= 0) continue - + const name = link.textContent?.trim() || row.textContent?.trim() if (!name || name.length === 0) continue - + if (!teachers.find(t => t.parseId === parseId)) { teachers.push({ parseId, name }) } } } } - - // Способ 3: Ищем все ссылки с obj= в URL (более общий поиск) - if (teachers.length === 0) { - const allLinks = Array.from(document.querySelectorAll('a[href*="obj="]')) - for (const link of allLinks) { - const href = link.getAttribute('href') - if (!href) continue - - // Проверяем, что это mn=3 (преподаватели) - if (!href.includes('mn=3')) continue - - const objMatch = href.match(/[?&]obj=(\d+)/) - if (!objMatch) continue - - const parseId = Number(objMatch[1]) - if (isNaN(parseId) || parseId <= 0) continue - - const name = link.textContent?.trim() - if (!name || name.length === 0) continue - - if (!teachers.find(t => t.parseId === parseId)) { - teachers.push({ parseId, name }) - } - } - } - - // Способ 4: Ищем в формах и input элементах - if (teachers.length === 0) { - const forms = Array.from(document.querySelectorAll('form[action*="mn=3"]')) - for (const form of forms) { - const action = form.getAttribute('action') || '' - const objMatch = action.match(/[?&]obj=(\d+)/) - if (objMatch) { - const parseId = Number(objMatch[1]) - if (!isNaN(parseId) && parseId > 0) { - const name = form.textContent?.trim() || `Преподаватель ${parseId}` - if (!teachers.find(t => t.parseId === parseId)) { - teachers.push({ parseId, name }) - } - } - } - } - } - + // Сортируем по имени teachers.sort((a, b) => a.name.localeCompare(b.name)) - + return teachers } diff --git a/src/pages/api/admin/settings.ts b/src/pages/api/admin/settings.ts index 70e9243..23fffe6 100644 --- a/src/pages/api/admin/settings.ts +++ b/src/pages/api/admin/settings.ts @@ -88,17 +88,11 @@ async function handler( ...(validatedDebug !== undefined && { debug: validatedDebug }) } - try { - saveSettings(settings) - // Сбрасываем кеш и загружаем свежие настройки для подтверждения - clearSettingsCache() - const savedSettings = loadSettings(true) - res.status(200).json({ success: true, settings: savedSettings }) - } catch (error) { - console.error('Error saving settings:', error) - const errorMessage = error instanceof Error ? error.message : 'Unknown error' - res.status(500).json({ error: `Failed to save settings: ${errorMessage}` }) - } + saveSettings(settings) + // Сбрасываем кеш и загружаем свежие настройки для подтверждения + clearSettingsCache() + const savedSettings = loadSettings(true) + res.status(200).json({ success: true, settings: savedSettings }) return } } diff --git a/src/pages/api/admin/teachers.ts b/src/pages/api/admin/teachers.ts index 104adce..26544c7 100644 --- a/src/pages/api/admin/teachers.ts +++ b/src/pages/api/admin/teachers.ts @@ -27,71 +27,31 @@ async function handler( // Парсинг и обновление списка преподавателей try { const url = `${PROXY_URL}/?mn=3` - console.log(`[Teachers API] Fetching teachers list from: ${url}`) - console.log(`[Teachers API] PROXY_URL: ${PROXY_URL}`) - + // Добавляем таймаут 10 секунд для fetch запроса const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 10000) - - const page = await fetch(url, { - signal: controller.signal, - redirect: 'follow', - headers: { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' - } - }) + + const page = await fetch(url, { signal: controller.signal }) clearTimeout(timeoutId) - console.log(`[Teachers API] Response status: ${page.status}`) - console.log(`[Teachers API] Response URL: ${page.url}`) - console.log(`[Teachers API] Response redirected: ${page.redirected}`) - + const content = await page.text() const contentType = page.headers.get('content-type') - console.log(`[Teachers API] Content length: ${content.length}, Content-Type: ${contentType}`) - + if (page.status !== 200 || !contentType || contentTypeParser.parse(contentType).type !== 'text/html') { - console.error(`[Teachers API] Invalid response: status ${page.status}, contentType: ${contentType}`) res.status(500).json({ error: `Failed to fetch teachers list: status ${page.status}` }) return } - // Проверяем, не редирект ли на страницу авторизации - if (content.includes('login') || content.includes('auth') || content.includes('Вход') || content.includes('Авторизация')) { - console.error('[Teachers API] Response appears to be a login page, not teachers list') - } - const dom = new JSDOM(content, { url }) const document = dom.window.document - - // Логируем заголовок страницы для отладки - const pageTitle = document.title - console.log(`[Teachers API] Page title: ${pageTitle}`) - - // Логируем немного HTML для отладки - const htmlPreview = content.substring(0, 500).replace(/\n/g, ' ') - console.log(`[Teachers API] HTML preview: ${htmlPreview}...`) - + const teachersList = parseTeachersList(document) - console.log(`[Teachers API] Parsed ${teachersList.length} teachers`) - + // Закрываем JSDOM для освобождения памяти dom.window.close() - + if (teachersList.length === 0) { - console.error('[Teachers API] No teachers found in HTML') - // Логируем больше информации для отладки - const hasMn3 = content.includes('mn=3') - const hasObj = content.includes('obj=') - const hasTeachersTable = content.includes('Преподаватель') || content.includes('преподавател') - console.log(`[Teachers API] HTML contains 'mn=3': ${hasMn3}, contains 'obj=': ${hasObj}, contains 'преподавател': ${hasTeachersTable}`) - - // Проверяем, не ошибка ли это - if (content.includes('Ошибка') || content.includes('Error') || content.includes('404') || content.includes('500')) { - console.error('[Teachers API] Response contains error indicators') - } - res.status(500).json({ error: 'No teachers found on the page' }) return } @@ -106,23 +66,20 @@ async function handler( name: teacher.name } } - console.log(`[Teachers API] Created TeachersData with ${Object.keys(teachersData).length} entries`) // Сохраняем в БД saveTeachers(teachersData) - console.log('[Teachers API] Saved teachers to database') - + // Сохраняем timestamp последнего обновления const { setTeachersLastUpdateTime } = await import('@/shared/data/database') setTeachersLastUpdateTime(Date.now()) - + // Сбрасываем кеш и загружаем свежие данные из БД clearTeachersCache() const updatedTeachers = loadTeachers(true) - console.log(`[Teachers API] Loaded ${Object.keys(updatedTeachers).length} teachers from database`) - - res.status(200).json({ - success: true, + + res.status(200).json({ + success: true, teachers: updatedTeachers, parsed: teachersList.length }) diff --git a/src/shared/data/database.ts b/src/shared/data/database.ts index ad0c974..466ed9f 100644 --- a/src/shared/data/database.ts +++ b/src/shared/data/database.ts @@ -9,37 +9,30 @@ import type { AppSettings } from './settings-loader' function getDatabaseDir(): string { // Если указан путь через переменную окружения, используем его if (process.env.DATABASE_DIR) { - console.log(`[Database] Using DATABASE_DIR from env: ${process.env.DATABASE_DIR}`) return process.env.DATABASE_DIR } - + // В production режиме (standalone) используем стандартный путь const cwd = process.cwd() - console.log(`[Database] process.cwd(): ${cwd}`) - + // Если мы в .next/standalone, поднимаемся на 2 уровня вверх к корню проекта if (cwd.includes('.next/standalone')) { // В standalone режиме process.cwd() = /opt/kspguti-schedule/.next/standalone // Нужно подняться до /opt/kspguti-schedule const standaloneMatch = cwd.match(/^(.+?)\/\.next\/standalone/) if (standaloneMatch && standaloneMatch[1]) { - console.log(`[Database] Detected standalone mode, using: ${standaloneMatch[1]}`) return standaloneMatch[1] } // Альтернативный способ: подняться на 2 уровня вверх - const parentDir = path.resolve(cwd, '..', '..') - console.log(`[Database] Fallback to parent directory: ${parentDir}`) - return parentDir + return path.resolve(cwd, '..', '..') } - + // Проверяем стандартный путь для production if (fs.existsSync('/opt/kspguti-schedule')) { - console.log('[Database] Using /opt/kspguti-schedule') return '/opt/kspguti-schedule' } - + // В development используем текущую директорию - console.log(`[Database] Using cwd: ${cwd}`) return cwd } @@ -51,8 +44,11 @@ const DEFAULT_PASSWORD = 'ksadmin' // Путь к старой базе данных (для миграции) const OLD_DB_PATH = path.join(DATABASE_DIR, 'data', 'schedule-app.db') -console.log(`[Database] DB_PATH: ${DB_PATH}`) -console.log(`[Database] dbDir: ${path.dirname(DB_PATH)}`) +// Создаем директорию db, если её нет +const dbDir = path.dirname(DB_PATH) +if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }) +} // Миграция базы данных из data/ в db/ (если старая база существует) function migrateDatabaseLocation(): void { @@ -60,7 +56,7 @@ function migrateDatabaseLocation(): void { if (fs.existsSync(DB_PATH)) { return } - + // Если старая база существует, перемещаем её if (fs.existsSync(OLD_DB_PATH)) { try { @@ -90,106 +86,31 @@ function migrateDatabaseLocation(): void { // Инициализация базы данных let db: Database.Database | null = null -let dbInitAttempted = false -let dbInitError: Error | null = null function getDatabase(): Database.Database { - // Если уже есть ошибка инициализации, выбрасываем её сразу - if (dbInitError) { - throw dbInitError - } - if (db) { return db } - // Защита от повторной инициализации при ошибке - if (dbInitAttempted) { - if (db) return db - throw new Error('Database initialization failed previously') - } - - dbInitAttempted = true - - console.log('[Database] Initializing database connection...') - console.log(`[Database] DB_PATH: ${DB_PATH}`) - console.log(`[Database] DB_PATH exists: ${fs.existsSync(DB_PATH)}`) - console.log(`[Database] process.cwd(): ${process.cwd()}`) - console.log(`[Database] DATABASE_DIR: ${DATABASE_DIR}`) - - // Создаем директорию db, если её нет - const dbDir = path.dirname(DB_PATH) - console.log(`[Database] dbDir: ${dbDir}`) - console.log(`[Database] dbDir exists: ${fs.existsSync(dbDir)}`) - - if (!fs.existsSync(dbDir)) { - console.log(`[Database] Creating directory: ${dbDir}`) - try { - fs.mkdirSync(dbDir, { recursive: true, mode: 0o755 }) - console.log(`[Database] Directory created successfully`) - } catch (error) { - const errMsg = `Failed to create database directory ${dbDir}: ${error}` - console.error(`[Database] ${errMsg}`) - dbInitError = new Error(errMsg) - throw dbInitError - } - } - - // Проверяем, можем ли записывать в директорию - try { - const testFile = path.join(dbDir, '.write-test-' + Date.now()) - fs.writeFileSync(testFile, 'test', { mode: 0o644 }) - fs.unlinkSync(testFile) - console.log('[Database] Directory is writable') - } catch (error) { - const errMsg = `Directory ${dbDir} is not writable: ${error}` - console.error(`[Database] ${errMsg}`) - dbInitError = new Error(errMsg) - throw new Error(errMsg) - } - // Выполняем миграцию расположения базы данных перед открытием migrateDatabaseLocation() - try { - console.log('[Database] Opening database...') - db = new Database(DB_PATH) - console.log('[Database] Database opened successfully') + db = new Database(DB_PATH) - // Проверяем, можем ли записывать - try { - db.exec('SELECT 1') - console.log('[Database] Database is writable') - } catch (error) { - const errMsg = `Database is not writable: ${(error as Error).message}` - console.error('[Database] ' + errMsg) - dbInitError = new Error(errMsg) - throw new Error(errMsg) - } + // Применяем современные настройки SQLite + db.pragma('journal_mode = WAL') // Write-Ahead Logging для лучшей производительности + db.pragma('synchronous = NORMAL') // Баланс между производительностью и надежностью + db.pragma('foreign_keys = ON') // Включение проверки внешних ключей + db.pragma('busy_timeout = 5000') // Таймаут для ожидания блокировок (5 секунд) + db.pragma('temp_store = MEMORY') // Хранение временных данных в памяти + db.pragma('mmap_size = 268435456') // Memory-mapped I/O (256MB) + db.pragma('cache_size = -64000') // Размер кеша в страницах (64MB) - // Применяем современные настройки SQLite - db.pragma('journal_mode = WAL') // Write-Ahead Logging для лучшей производительности - db.pragma('synchronous = NORMAL') // Баланс между производительностью и надежностью - db.pragma('foreign_keys = ON') // Включение проверки внешних ключей - db.pragma('busy_timeout = 5000') // Таймаут для ожидания блокировок (5 секунд) - db.pragma('temp_store = MEMORY') // Хранение временных данных в памяти - db.pragma('mmap_size = 268435456') // Memory-mapped I/O (256MB) - db.pragma('cache_size = -64000') // Размер кеша в страницах (64MB) + // Создаем таблицы, если их нет + initializeTables() - console.log('[Database] SQLite pragmas applied') - - // Создаем таблицы, если их нет - initializeTables() - - // Выполняем миграцию данных из JSON, если БД пустая - migrateFromJSON() - - console.log('[Database] Database initialization complete') - } catch (error) { - console.error('[Database] Failed to initialize database:', error) - dbInitError = error as Error - throw error - } + // Выполняем миграцию данных из JSON, если БД пустая + migrateFromJSON() return db } @@ -330,7 +251,6 @@ export function getAllTeachers(): TeachersData { } } - console.log(`[Database] getAllTeachers: found ${Object.keys(teachers).length} teachers`) return teachers } @@ -672,48 +592,6 @@ function migrateFromJSON(): void { console.error('Error hashing default password:', err) } } - - // Мигрируем преподавателей из teachers.ts, если БД пустая - const teachersCount = database.prepare('SELECT COUNT(*) as count FROM teachers').get() as { count: number } - if (teachersCount.count === 0) { - try { - // Пытаемся импортировать преподавателей из teachers.ts - const possiblePaths = [ - path.join(process.cwd(), 'src/shared/data/teachers.ts'), - path.join(process.cwd(), '.next/standalone/src/shared/data/teachers.ts'), - path.join(process.cwd(), 'teachers.ts') - ] - - for (const filePath of possiblePaths) { - if (fs.existsSync(filePath)) { - console.log(`Migrating teachers from ${filePath}...`) - // Читаем файл и извлекаем JSON массив - const fileContents = fs.readFileSync(filePath, 'utf8') - const jsonMatch = fileContents.match(/export const teachers = (\[[\s\S]*?\])/) - if (jsonMatch && jsonMatch[1]) { - const teachersArray = JSON.parse(jsonMatch[1]) as Array<{ name: string }> - - const insertStmt = database.prepare('INSERT INTO teachers (id, parseId, name) VALUES (?, ?, ?)') - const transaction = database.transaction((teachers: Array<{ name: string }>) => { - teachers.forEach((teacher, index) => { - if (teacher.name) { - // Используем индекс как parseId, так как в teachers.ts нет parseId - const id = String(index + 1) - insertStmt.run(id, index + 1, teacher.name) - } - }) - }) - - transaction(teachersArray) - console.log(`Teachers migrated from teachers.ts: ${teachersArray.length} teachers`) - break - } - } - } - } catch (error) { - console.error('Error migrating teachers from teachers.ts:', error) - } - } } // Экспортируем функцию для закрытия соединения (полезно для тестов) diff --git a/src/shared/data/teachers-loader.ts b/src/shared/data/teachers-loader.ts index 08f086a..6e4e205 100644 --- a/src/shared/data/teachers-loader.ts +++ b/src/shared/data/teachers-loader.ts @@ -11,7 +11,7 @@ const CACHE_TTL_MS = 1000 * 60 // 1 минута export function loadTeachers(forceRefresh: boolean = false): TeachersData { const now = Date.now() const isCacheValid = cachedTeachers !== null && !forceRefresh && (now - cacheTimestamp) < CACHE_TTL_MS - + if (isCacheValid && cachedTeachers !== null) { return cachedTeachers } @@ -19,7 +19,6 @@ export function loadTeachers(forceRefresh: boolean = false): TeachersData { try { cachedTeachers = getAllTeachersFromDB() cacheTimestamp = now - console.log(`[TeachersLoader] Loaded ${Object.keys(cachedTeachers).length} teachers from database`) return cachedTeachers } catch (error) { console.error('Error loading teachers from database:', error) diff --git a/systemd/kspguti-schedule.service b/systemd/kspguti-schedule.service index 796d796..f98bb2e 100644 --- a/systemd/kspguti-schedule.service +++ b/systemd/kspguti-schedule.service @@ -9,7 +9,6 @@ Group=www-data WorkingDirectory=/opt/kspguti-schedule/.next/standalone Environment=NODE_ENV=production Environment=NEXT_TELEMETRY_DISABLED=1 -Environment=DATABASE_DIR=/opt/kspguti-schedule Environment=PORT=3000 Environment=HOSTNAME=0.0.0.0 # Uncomment and set your environment variables: