import { Injectable, NotFoundException, ForbiddenException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Course } from '../entities/course.entity'; import { UserCourse } from '../entities/user-course.entity'; import { UserStar } from '../entities/user-star.entity'; import { Comment } from '../entities/comment.entity'; @Injectable() export class CoursesService { constructor( @InjectRepository(Course) private courseRepository: Repository, @InjectRepository(UserCourse) private userCourseRepository: Repository, @InjectRepository(UserStar) private userStarRepository: Repository, @InjectRepository(Comment) private commentRepository: Repository, ) {} async findAll() { return this.courseRepository.find({ where: { isActive: true }, select: [ 'id', 'title', 'description', 'coverImage', 'price', 'category', 'viewCount', 'likeCount', 'starCount', ], order: { createdAt: 'DESC' }, }); } async findOne(id: number) { const course = await this.courseRepository.findOne({ where: { id, isActive: true }, select: [ 'id', 'title', 'description', 'coverImage', 'price', 'category', 'viewCount', 'likeCount', 'starCount', ], }); if (!course) { throw new NotFoundException('Course not found'); } return course; } async incrementViewCount(id: number) { await this.courseRepository.increment({ id }, 'viewCount', 1); } async toggleLike(id: number) { const course = await this.courseRepository.findOne({ where: { id } }); if (course) { await this.courseRepository.increment({ id }, 'likeCount', 1); } } async toggleStar(courseId: number, userId: number) { const existingStar = await this.userStarRepository.findOne({ where: { courseId, userId }, }); if (existingStar) { await this.userStarRepository.remove(existingStar); await this.courseRepository.decrement({ id: courseId }, 'starCount', 1); } else { const newStar = this.userStarRepository.create({ courseId, userId }); await this.userStarRepository.save(newStar); await this.courseRepository.increment({ id: courseId }, 'starCount', 1); } } async getUserStars(userId: number) { const userStars = await this.userStarRepository.find({ where: { userId }, relations: ['course'], }); return userStars.map((us) => ({ id: us.course.id, title: us.course.title, coverImage: us.course.coverImage, description: us.course.description, price: us.course.price, category: us.course.category, viewCount: us.course.viewCount, likeCount: us.course.likeCount, starCount: us.course.starCount, })); } async getUserCourses(userId: number) { const userCourses = await this.userCourseRepository.find({ where: { userId }, relations: ['course'], }); return userCourses.map((uc) => ({ id: uc.course.id, title: uc.course.title, coverImage: uc.course.coverImage, expiredAt: uc.expiredAt, })); } async getComments(courseId: number) { const comments = await this.commentRepository.find({ where: { courseId }, relations: ['user', 'replyToComment', 'replyToComment.user'], order: { createdAt: 'ASC' }, }); return comments.map(c => ({ id: c.id, courseId: c.courseId, userId: c.userId, parentId: c.parentId, replyToCommentId: c.replyToCommentId, content: c.content, createdAt: c.createdAt, user: { id: c.user.id, nickname: c.user.nickname, avatar: c.user.avatar, isVip: c.user.isVip, }, replyToUser: c.replyToComment ? { id: c.replyToComment.user.id, nickname: c.replyToComment.user.nickname, } : undefined })); } async addComment(courseId: number, userId: number, content: string, parentId?: number, replyToCommentId?: number) { const course = await this.courseRepository.findOne({ where: { id: courseId } }); if (!course) throw new NotFoundException('Course not found'); const comment = this.commentRepository.create({ courseId, userId, content, parentId, replyToCommentId, }); await this.commentRepository.save(comment); const savedComment = await this.commentRepository.findOne({ where: { id: comment.id }, relations: ['user', 'replyToComment', 'replyToComment.user'] }); if (!savedComment) throw new NotFoundException('Comment not found after save'); return { id: savedComment.id, courseId: savedComment.courseId, userId: savedComment.userId, parentId: savedComment.parentId, replyToCommentId: savedComment.replyToCommentId, content: savedComment.content, createdAt: savedComment.createdAt, user: { id: savedComment.user.id, nickname: savedComment.user.nickname, avatar: savedComment.user.avatar, isVip: savedComment.user.isVip, }, replyToUser: savedComment.replyToComment ? { id: savedComment.replyToComment.user.id, nickname: savedComment.replyToComment.user.nickname, } : undefined }; } async getCourseAccess(userId: number, courseId: number) { const userCourse = await this.userCourseRepository.findOne({ where: { userId, courseId }, relations: ['course'], }); if (!userCourse) { throw new ForbiddenException('You have not purchased this course'); } if (userCourse.expiredAt < new Date()) { throw new ForbiddenException('Your access to this course has expired'); } return { hasAccess: true, driveLink: userCourse.course.driveLink, // In a real app, this should be a dynamic signed URL expiredAt: userCourse.expiredAt, }; } }