Spaces:
Sleeping
Sleeping
| 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'; | |
| () | |
| export class CoursesService { | |
| constructor( | |
| (Course) | |
| private courseRepository: Repository<Course>, | |
| (UserCourse) | |
| private userCourseRepository: Repository<UserCourse>, | |
| (UserStar) | |
| private userStarRepository: Repository<UserStar>, | |
| (Comment) | |
| private commentRepository: Repository<Comment>, | |
| ) {} | |
| 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, | |
| }; | |
| } | |
| } | |