127 lines
3.8 KiB
TypeScript
Executable File
127 lines
3.8 KiB
TypeScript
Executable File
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<Review>,
|
|
) {}
|
|
|
|
async createReview(createReviewDto: CreateReviewDto): Promise<Review> {
|
|
// 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<number, number>;
|
|
}> {
|
|
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<number, number> = {};
|
|
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,
|
|
};
|
|
}
|
|
}
|