import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Review } from '../../entities/review.entity'; import { CreateReviewDto } from './dto/create-review.dto'; @Injectable() export class AnalyticsService { constructor( @InjectRepository(Review) private readonly reviewRepository: Repository, ) {} async createReview(createReviewDto: CreateReviewDto): Promise { // Check if user already reviewed this item const existingReview = await this.reviewRepository.findOne({ where: { userId: createReviewDto.userId, reviewableType: createReviewDto.reviewableType, reviewableId: createReviewDto.reviewableId, }, }); if (existingReview) { throw new ConflictException('You have already reviewed this item'); } const review = this.reviewRepository.create(createReviewDto); return this.reviewRepository.save(review); } async findReviewsForItem( reviewableType: string, reviewableId: string, page: number = 1, limit: number = 10, ): Promise<{ reviews: Review[]; total: number; averageRating: number; ratingDistribution: Record; }> { const [reviews, total] = await this.reviewRepository.findAndCount({ where: { reviewableType, reviewableId }, relations: ['user'], skip: (page - 1) * limit, take: limit, order: { createdAt: 'DESC' }, }); // Calculate average rating const averageResult = await this.reviewRepository .createQueryBuilder('review') .select('AVG(review.rating)', 'average') .where('review.reviewableType = :type AND review.reviewableId = :id', { type: reviewableType, id: reviewableId, }) .getRawOne(); const averageRating = parseFloat(averageResult.average) || 0; // Calculate rating distribution const distributionResult = await this.reviewRepository .createQueryBuilder('review') .select('review.rating', 'rating') .addSelect('COUNT(*)', 'count') .where('review.reviewableType = :type AND review.reviewableId = :id', { type: reviewableType, id: reviewableId, }) .groupBy('review.rating') .getRawMany(); const ratingDistribution: Record = {}; for (let i = 1; i <= 5; i++) { ratingDistribution[i] = 0; } distributionResult.forEach(item => { ratingDistribution[item.rating] = parseInt(item.count); }); return { reviews, total, averageRating, ratingDistribution }; } async getAnalyticsOverview(): Promise<{ totalReviews: number; averageRating: number; reviewsByType: Array<{ type: string; count: number; avgRating: number }>; recentReviews: Review[]; }> { const totalReviews = await this.reviewRepository.count(); const averageResult = await this.reviewRepository .createQueryBuilder('review') .select('AVG(review.rating)', 'average') .getRawOne(); const averageRating = parseFloat(averageResult.average) || 0; const reviewsByType = await this.reviewRepository .createQueryBuilder('review') .select('review.reviewableType', 'type') .addSelect('COUNT(*)', 'count') .addSelect('AVG(review.rating)', 'avgRating') .groupBy('review.reviewableType') .getRawMany(); const recentReviews = await this.reviewRepository.find({ relations: ['user'], order: { createdAt: 'DESC' }, take: 10, }); return { totalReviews, averageRating, reviewsByType: reviewsByType.map(item => ({ type: item.type, count: parseInt(item.count), avgRating: parseFloat(item.avgRating), })), recentReviews, }; } }