import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../prisma/prisma.service'; @Injectable() export class CoursesService { constructor(private prisma: PrismaService) {} async findAll(params: { page?: number; pageSize?: number; categoryId?: number; isFree?: boolean }) { const page = Number(params.page ?? 1); const pageSize = Number(params.pageSize ?? 20); const { categoryId, isFree } = params; const where: any = { status: 'PUBLISHED', deletedAt: null }; if (categoryId) where.categoryId = categoryId; if (isFree !== undefined) where.isFree = isFree; const [items, total] = await Promise.all([ this.prisma.course.findMany({ where, skip: (page - 1) * pageSize, take: pageSize, orderBy: { sortOrder: 'asc' }, include: { category: true, chapters: { include: { lessons: true }, orderBy: { sortOrder: 'asc' } } }, }), this.prisma.course.count({ where }), ]); return { items, total, page, pageSize }; } async findById(id: number) { return this.prisma.course.findUnique({ where: { id }, include: { category: true, chapters: { orderBy: { sortOrder: 'asc' }, include: { lessons: { orderBy: { sortOrder: 'asc' } } }, }, }, }); } async create(data: { title: string; description?: string; cover?: string; categoryId?: number; price?: number; isFree?: boolean; sortOrder?: number; }) { return this.prisma.course.create({ data }); } async update(id: number, data: any) { const { chapters, ...courseData } = data; return this.prisma.course.update({ where: { id }, data: courseData, }); } async updateWithChapters(id: number, data: any) { const { chapters, ...courseData } = data; if (chapters) { const existingChapters = await this.prisma.chapter.findMany({ where: { courseId: id } }); const existingIds = existingChapters.map(c => c.id); const incomingIds = chapters.filter((c: any) => c.id).map((c: any) => c.id); const toDelete = existingIds.filter(eid => !incomingIds.includes(eid)); for (const cid of toDelete) { await this.prisma.lesson.deleteMany({ where: { chapterId: cid } }); await this.prisma.chapter.delete({ where: { id: cid } }); } for (const ch of chapters) { if (ch.id) { const { lessons, id: chId, courseId, ...chData } = ch; await this.prisma.chapter.update({ where: { id: ch.id }, data: chData }); if (lessons) { const existingLessons = await this.prisma.lesson.findMany({ where: { chapterId: ch.id } }); const existingLessonIds = existingLessons.map(l => l.id); const incomingLessonIds = lessons.filter((l: any) => l.id).map((l: any) => l.id); const lessonsToDelete = existingLessonIds.filter(eid => !incomingLessonIds.includes(eid)); for (const lid of lessonsToDelete) { await this.prisma.lesson.delete({ where: { id: lid } }); } for (const le of lessons) { if (le.id) { const { id: lessonId, chapterId, ...lessonData } = le; await this.prisma.lesson.update({ where: { id: le.id }, data: lessonData }); } else { const { id: _li, ...lessonData } = le; await this.prisma.lesson.create({ data: { ...lessonData, chapterId: ch.id } }); } } } } else { const { lessons, id: _newChId, ...chData } = ch; const newCh = await this.prisma.chapter.create({ data: { ...chData, courseId: id } }); if (lessons) { for (const le of lessons) { const { id: _li, ...lessonData } = le; await this.prisma.lesson.create({ data: { ...lessonData, chapterId: newCh.id } }); } } } } } return this.prisma.course.update({ where: { id }, data: courseData, include: { chapters: { include: { lessons: { orderBy: { sortOrder: 'asc' } } }, orderBy: { sortOrder: 'asc' } } }, }); } async remove(id: number) { return this.prisma.course.update({ where: { id }, data: { deletedAt: new Date() } }); } async updateProgress(userId: number, courseId: number, lessonId: number, completed?: boolean, progress?: number) { const existing = await this.prisma.learnRecord.findUnique({ where: { userId_lessonId: { userId, lessonId } }, }); if (existing) { return this.prisma.learnRecord.update({ where: { id: existing.id }, data: { completed: completed ?? existing.completed, progress: progress ?? existing.progress, }, }); } else { return this.prisma.learnRecord.create({ data: { userId, courseId, lessonId, completed: completed ?? false, progress: progress ?? 0, }, }); } } async getLearningProgress(userId: number, courseId?: number) { const where: any = { userId }; if (courseId) { where.courseId = courseId; } const records = await this.prisma.learnRecord.findMany({ where, include: { course: { select: { id: true, title: true } }, lesson: { select: { id: true, title: true, chapterId: true } }, }, }); const courseMap: Record = {}; records.forEach(r => { if (!courseMap[r.courseId]) { courseMap[r.courseId] = { courseId: r.courseId, courseTitle: r.course.title, totalLessons: 0, completedLessons: 0, progress: 0, records: [], }; } courseMap[r.courseId].records.push(r); courseMap[r.courseId].totalLessons++; if (r.completed) courseMap[r.courseId].completedLessons++; }); Object.values(courseMap).forEach(c => { c.progress = c.totalLessons > 0 ? Math.round((c.completedLessons / c.totalLessons) * 100) : 0; }); return { items: Object.values(courseMap), total: records.length, }; } }