fix(groups): исправить синхронизацию и транзакции БД
- Обернуть saveGroups() и saveTeachers() в транзакции SQLite для атомарности операций
- Экспортировать getDatabase() для использования в других модулях
- Исправить логику синхронизации в loadGroups() для режима SCHED_MODE=kspsuti
- Удалить дублирующие вызовы clearGroupsCache() и clearTeachersCache() из API handlers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { withAuth, ApiResponse } from '@/shared/utils/api-wrapper'
|
||||
import { loadGroups, saveGroups, clearGroupsCache, GroupsData } from '@/shared/data/groups-loader'
|
||||
import { loadGroups, saveGroups, GroupsData } from '@/shared/data/groups-loader'
|
||||
import { validateGroupId, validateCourse } from '@/shared/utils/validation'
|
||||
import { SCHED_MODE } from '@/shared/constants/urls'
|
||||
|
||||
@@ -19,7 +19,6 @@ async function handler(
|
||||
|
||||
if (req.method === 'GET') {
|
||||
// Получение списка групп (всегда свежие данные для админ-панели)
|
||||
clearGroupsCache()
|
||||
const groups = await loadGroups(true)
|
||||
res.status(200).json({ groups })
|
||||
return
|
||||
@@ -72,8 +71,7 @@ async function handler(
|
||||
}
|
||||
|
||||
saveGroups(groups)
|
||||
// Сбрасываем кеш и загружаем свежие данные из БД
|
||||
clearGroupsCache()
|
||||
// Загружаем свежие данные из БД (кеш уже сброшен в saveGroups)
|
||||
const updatedGroups = await loadGroups(true)
|
||||
res.status(200).json({ success: true, groups: updatedGroups })
|
||||
return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { withAuth, ApiResponse } from '@/shared/utils/api-wrapper'
|
||||
import { loadGroups, saveGroups, clearGroupsCache, GroupsData } from '@/shared/data/groups-loader'
|
||||
import { loadGroups, saveGroups, GroupsData } from '@/shared/data/groups-loader'
|
||||
import { validateCourse } from '@/shared/utils/validation'
|
||||
import { SCHED_MODE } from '@/shared/constants/urls'
|
||||
|
||||
@@ -60,8 +60,7 @@ async function handler(
|
||||
}
|
||||
|
||||
saveGroups(groups)
|
||||
// Сбрасываем кеш и загружаем свежие данные из БД
|
||||
clearGroupsCache()
|
||||
// Загружаем свежие данные из БД (кеш уже сброшен в saveGroups)
|
||||
const updatedGroups = await loadGroups(true)
|
||||
res.status(200).json({ success: true, groups: updatedGroups })
|
||||
return
|
||||
@@ -77,8 +76,7 @@ async function handler(
|
||||
delete groups[id]
|
||||
|
||||
saveGroups(groups)
|
||||
// Сбрасываем кеш и загружаем свежие данные из БД
|
||||
clearGroupsCache()
|
||||
// Загружаем свежие данные из БД (кеш уже сброшен в saveGroups)
|
||||
const updatedGroups = await loadGroups(true)
|
||||
res.status(200).json({ success: true, groups: updatedGroups })
|
||||
return
|
||||
|
||||
@@ -17,7 +17,6 @@ async function handler(
|
||||
) {
|
||||
if (req.method === 'GET') {
|
||||
// Получение списка преподавателей (всегда свежие данные для админ-панели)
|
||||
clearTeachersCache()
|
||||
const teachers = loadTeachers(true)
|
||||
res.status(200).json({ teachers })
|
||||
return
|
||||
@@ -74,8 +73,7 @@ async function handler(
|
||||
const { setTeachersLastUpdateTime } = await import('@/shared/data/database')
|
||||
setTeachersLastUpdateTime(Date.now())
|
||||
|
||||
// Сбрасываем кеш и загружаем свежие данные из БД
|
||||
clearTeachersCache()
|
||||
// Загружаем свежие данные из БД (кеш уже сброшен в saveTeachers)
|
||||
const updatedTeachers = loadTeachers(true)
|
||||
|
||||
res.status(200).json({
|
||||
|
||||
@@ -91,7 +91,7 @@ function migrateDatabaseLocation(): void {
|
||||
// Инициализация базы данных
|
||||
let db: Database.Database | null = null
|
||||
|
||||
function getDatabase(): Database.Database {
|
||||
export function getDatabase(): Database.Database {
|
||||
if (db) {
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getAllGroups as getAllGroupsFromDB, createGroup, updateGroup, deleteGroup, getGroup } from './database'
|
||||
import { getAllGroups as getAllGroupsFromDB, createGroup, updateGroup, deleteGroup, getGroup, getDatabase } from './database'
|
||||
import { SCHED_MODE } from '@/shared/constants/urls'
|
||||
import { syncGroupsFromKspsutiIfNeeded } from '@/app/agregator/groups'
|
||||
import type { Database } from 'better-sqlite3'
|
||||
|
||||
export type GroupInfo = {
|
||||
parseId: number
|
||||
@@ -24,17 +25,19 @@ export async function loadGroups(forceRefresh: boolean = false): Promise<GroupsD
|
||||
const now = Date.now()
|
||||
const isCacheValid = cachedGroups !== null && !forceRefresh && (now - cacheTimestamp) < CACHE_TTL_MS
|
||||
|
||||
if (isCacheValid && cachedGroups !== null) {
|
||||
return cachedGroups
|
||||
}
|
||||
|
||||
// В авто‑режиме сначала пробуем синхронизировать группы с lk.ks.psuti.ru.
|
||||
// В режиме kspsuti всегда проверяем синхронизацию, даже если кэш валиден
|
||||
if (SCHED_MODE === 'kspsuti') {
|
||||
const synced = await syncGroupsFromKspsutiIfNeeded(KSPSUTI_SYNC_TTL_MS)
|
||||
if (synced) {
|
||||
saveGroups(synced)
|
||||
clearGroupsCache()
|
||||
// После saveGroups кеш уже сброшен, продолжаем загрузку из БД
|
||||
} else if (isCacheValid && cachedGroups !== null) {
|
||||
// Синхронизация не проводилась (TTL не истёк), используем кэш
|
||||
return cachedGroups
|
||||
}
|
||||
} else if (isCacheValid && cachedGroups !== null) {
|
||||
// В других режимах используем обычную логику кэширования
|
||||
return cachedGroups
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -59,23 +62,34 @@ export function saveGroups(groups: GroupsData): void {
|
||||
const existingIds = new Set(Object.keys(existingGroups))
|
||||
const newIds = new Set(Object.keys(groups))
|
||||
|
||||
// Добавляем или обновляем группы
|
||||
for (const [id, group] of Object.entries(groups)) {
|
||||
if (existingIds.has(id)) {
|
||||
updateGroup(id, group)
|
||||
} else {
|
||||
createGroup(id, group)
|
||||
}
|
||||
}
|
||||
// Получаем ссылки на подготовленные выражения для транзакции
|
||||
const database = getDatabase() as Database
|
||||
const insertStmt = database.prepare('INSERT INTO groups (id, parseId, name, course) VALUES (?, ?, ?, ?)')
|
||||
const updateStmt = database.prepare('UPDATE groups SET parseId = ?, name = ?, course = ? WHERE id = ?')
|
||||
const deleteStmt = database.prepare('DELETE FROM groups WHERE id = ?')
|
||||
|
||||
// Удаляем группы, которых больше нет
|
||||
for (const id of existingIds) {
|
||||
if (!newIds.has(id)) {
|
||||
deleteGroup(id)
|
||||
// Выполняем все операции в транзакции для атомарности
|
||||
const saveTransaction = database.transaction((groupsData: GroupsData) => {
|
||||
// Добавляем или обновляем группы
|
||||
for (const [id, group] of Object.entries(groupsData)) {
|
||||
if (existingIds.has(id)) {
|
||||
updateStmt.run(group.parseId, group.name, group.course, id)
|
||||
} else {
|
||||
insertStmt.run(id, group.parseId, group.name, group.course)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Сбрасываем кеш и timestamp
|
||||
// Удаляем группы, которых больше нет
|
||||
for (const id of existingIds) {
|
||||
if (!newIds.has(id)) {
|
||||
deleteStmt.run(id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
saveTransaction(groups)
|
||||
|
||||
// Сбрасываем кеш и timestamp после успешной транзакции
|
||||
cachedGroups = null
|
||||
cacheTimestamp = 0
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getAllTeachers as getAllTeachersFromDB, createTeacher, updateTeacher, deleteTeacher, getTeacher, type TeacherInfo, type TeachersData } from './database'
|
||||
import { getAllTeachers as getAllTeachersFromDB, createTeacher, updateTeacher, deleteTeacher, getTeacher, getDatabase, type TeacherInfo, type TeachersData } from './database'
|
||||
import type { Database } from 'better-sqlite3'
|
||||
|
||||
let cachedTeachers: TeachersData | null = null
|
||||
let cacheTimestamp: number = 0
|
||||
@@ -38,23 +39,34 @@ export function saveTeachers(teachers: TeachersData): void {
|
||||
const existingIds = new Set(Object.keys(existingTeachers))
|
||||
const newIds = new Set(Object.keys(teachers))
|
||||
|
||||
// Добавляем или обновляем преподавателей
|
||||
for (const [id, teacher] of Object.entries(teachers)) {
|
||||
if (existingIds.has(id)) {
|
||||
updateTeacher(id, teacher)
|
||||
} else {
|
||||
createTeacher(id, teacher)
|
||||
}
|
||||
}
|
||||
// Получаем ссылки на подготовленные выражения для транзакции
|
||||
const database = getDatabase() as Database
|
||||
const insertStmt = database.prepare('INSERT INTO teachers (id, parseId, name) VALUES (?, ?, ?)')
|
||||
const updateStmt = database.prepare('UPDATE teachers SET parseId = ?, name = ? WHERE id = ?')
|
||||
const deleteStmt = database.prepare('DELETE FROM teachers WHERE id = ?')
|
||||
|
||||
// Удаляем преподавателей, которых больше нет
|
||||
for (const id of existingIds) {
|
||||
if (!newIds.has(id)) {
|
||||
deleteTeacher(id)
|
||||
// Выполняем все операции в транзакции для атомарности
|
||||
const saveTransaction = database.transaction((teachersData: TeachersData) => {
|
||||
// Добавляем или обновляем преподавателей
|
||||
for (const [id, teacher] of Object.entries(teachersData)) {
|
||||
if (existingIds.has(id)) {
|
||||
updateStmt.run(teacher.parseId, teacher.name, id)
|
||||
} else {
|
||||
insertStmt.run(id, teacher.parseId, teacher.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Сбрасываем кеш и timestamp
|
||||
// Удаляем преподавателей, которых больше нет
|
||||
for (const id of existingIds) {
|
||||
if (!newIds.has(id)) {
|
||||
deleteStmt.run(id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
saveTransaction(teachers)
|
||||
|
||||
// Сбрасываем кеш и timestamp после успешной транзакции
|
||||
cachedTeachers = null
|
||||
cacheTimestamp = 0
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user