diff --git a/Backend/src/services/ChaptersLesson.service.ts b/Backend/src/services/ChaptersLesson.service.ts index 41b401c6..685642be 100644 --- a/Backend/src/services/ChaptersLesson.service.ts +++ b/Backend/src/services/ChaptersLesson.service.ts @@ -483,10 +483,22 @@ export class ChaptersLessonService { if (currentLesson.chapter_id !== chapter_id) { throw new NotFoundError('Lesson not found in this chapter'); } - const oldSortOrder = currentLesson.sort_order; + + // First, normalize sort_order to fix any gaps or duplicates + await this.normalizeLessonSortOrder(chapter_id); + + // Re-fetch the lesson to get updated sort_order after normalization + const normalizedLesson = await prisma.lesson.findUnique({ where: { id: lesson_id } }); + if (!normalizedLesson) throw new NotFoundError('Lesson not found'); + + const oldSortOrder = normalizedLesson.sort_order; + + // Get total lesson count to validate sort_order (1-based) + const lessonCount = await prisma.lesson.count({ where: { chapter_id } }); + const validNewSortOrder = Math.max(1, Math.min(newSortOrder, lessonCount)); // If same position, no need to reorder - if (oldSortOrder === newSortOrder) { + if (oldSortOrder === validNewSortOrder) { const lessons = await prisma.lesson.findMany({ where: { chapter_id }, orderBy: { sort_order: 'asc' } @@ -495,12 +507,12 @@ export class ChaptersLessonService { } // Shift other lessons to make room for the insert - if (oldSortOrder > newSortOrder) { + if (oldSortOrder > validNewSortOrder) { // Moving up: shift lessons between newSortOrder and oldSortOrder-1 down by 1 await prisma.lesson.updateMany({ where: { chapter_id, - sort_order: { gte: newSortOrder, lt: oldSortOrder } + sort_order: { gte: validNewSortOrder, lt: oldSortOrder } }, data: { sort_order: { increment: 1 } } }); @@ -509,14 +521,14 @@ export class ChaptersLessonService { await prisma.lesson.updateMany({ where: { chapter_id, - sort_order: { gt: oldSortOrder, lte: newSortOrder } + sort_order: { gt: oldSortOrder, lte: validNewSortOrder } }, data: { sort_order: { decrement: 1 } } }); } // Update the target lesson to the new position - await prisma.lesson.update({ where: { id: lesson_id }, data: { sort_order: newSortOrder } }); + await prisma.lesson.update({ where: { id: lesson_id }, data: { sort_order: validNewSortOrder } }) // Fetch all lessons with updated order const lessons = await prisma.lesson.findMany({ @@ -576,10 +588,16 @@ export class ChaptersLessonService { } } + // Get chapter_id before deletion for normalization + const chapterId = lesson.chapter_id; + // Delete lesson (CASCADE will delete: attachments, quiz, questions, choices) // Based on Prisma schema: onDelete: Cascade await prisma.lesson.delete({ where: { id: lesson_id } }); + // Normalize sort_order for remaining lessons (fill gaps) + await this.normalizeLessonSortOrder(chapterId); + return { code: 200, message: 'Lesson deleted successfully' }; } catch (error) { logger.error(`Error deleting lesson: ${error}`); @@ -1219,6 +1237,34 @@ export class ChaptersLessonService { logger.info(`Normalized sort_order for quiz ${quizId}: ${questions.length} questions`); } + /** + * Normalize sort_order for all lessons in a chapter + * Ensures sort_order is sequential starting from 1 with no gaps or duplicates + * + * @param chapterId Chapter ID to normalize + */ + private async normalizeLessonSortOrder(chapterId: number): Promise { + // Get all lessons ordered by current sort_order + const lessons = await prisma.lesson.findMany({ + where: { chapter_id: chapterId }, + orderBy: { sort_order: 'asc' }, + select: { id: true, sort_order: true } + }); + + if (lessons.length === 0) return; + + // Update each lesson with sequential sort_order starting from 1 + const updates = lessons.map((lesson, index) => + prisma.lesson.update({ + where: { id: lesson.id }, + data: { sort_order: index + 1 } + }) + ); + + await prisma.$transaction(updates); + logger.info(`Normalized sort_order for chapter ${chapterId}: ${lessons.length} lessons`); + } + /** * อัพเดตการตั้งค่า Quiz * Update quiz settings (title, passing_score, time_limit, etc.)