@devx-commerce/plugin-product-reviews
Version:
Product Reviews Plugin for Medusa
139 lines • 11.5 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@medusajs/framework/utils");
const models_1 = require("./models");
const DEFAULT_CONFIGS = [
{ key: "AUTO_PUBLISH", value: false },
{ key: "VERIFIED_PURCHASES_ONLY", value: false },
];
class ProductReviewService extends (0, utils_1.MedusaService)({
ProductReview: models_1.ProductReviewModel,
ProductReviewResponse: models_1.ProductReviewResponseModel,
ProductReviewStats: models_1.ProductReviewStatsModel,
ReviewAssociatedMedia: models_1.ReviewAssociatedMediaModel,
ProductReviewConfig: models_1.ProductReviewConfigModel,
}) {
constructor(container) {
super(container);
this.ensureDefaultConfigs();
}
async ensureDefaultConfigs() {
const existingConfigs = await this.listProductReviewConfigs();
for (const defaultConfig of DEFAULT_CONFIGS) {
const configExists = existingConfigs.some((config) => config.key === defaultConfig.key);
if (!configExists) {
await this.createProductReviewConfigs(defaultConfig);
}
}
}
async refreshProductReviewStats(productIds, sharedContext) {
const calculatedStats = await this.calculateProductReviewStats(productIds, sharedContext);
const foundStats = await this.listProductReviewStats({ product_id: productIds }, {});
// Group existing stats by product_id to handle duplicates
const statsByProduct = foundStats.reduce((acc, stat) => {
if (!acc[stat.product_id]) {
acc[stat.product_id] = [];
}
acc[stat.product_id].push(stat);
return acc;
}, {});
const updatedStats = await Promise.all(calculatedStats.map(async (calculatedStat) => {
const existingStats = statsByProduct[calculatedStat.product_id] || [];
// If there are multiple existing stats, set all of them to 0 first
if (existingStats.length > 1) {
await Promise.all(existingStats.map((stat) => this.updateProductReviewStats({
id: stat.id,
product_id: stat.product_id,
review_count: 0,
average_rating: 0,
rating_count_1: 0,
rating_count_2: 0,
rating_count_3: 0,
rating_count_4: 0,
rating_count_5: 0,
})));
}
if (existingStats.length > 0) {
// Use the first existing stat (or the only one after setting others to 0)
const foundStat = existingStats[0];
return await this.updateProductReviewStats({
id: foundStat.id,
...calculatedStat,
});
}
return await this.createProductReviewStats(calculatedStat);
}));
// Handle products that have existing stats but no calculated stats (should be set to 0)
const calculatedProductIds = new Set(calculatedStats.map((stat) => stat.product_id));
const productsToDelete = Object.keys(statsByProduct).filter((productId) => !calculatedProductIds.has(productId));
if (productsToDelete.length > 0) {
for (const productId of productsToDelete) {
const existingStats = statsByProduct[productId];
await Promise.all(existingStats.map((stat) => this.updateProductReviewStats({
id: stat.id,
product_id: stat.product_id,
review_count: 0,
average_rating: 0,
rating_count_1: 0,
rating_count_2: 0,
rating_count_3: 0,
rating_count_4: 0,
rating_count_5: 0,
})));
}
}
return updatedStats;
}
async calculateProductReviewStats(productIds, sharedContext) {
let whereClause = `
pr.is_admin_verified = true
AND pr.deleted_at IS NULL
`;
if (productIds.length > 0) {
const ids = productIds.map((id) => `'${id}'`).join(", ");
whereClause = `
pr.product_id IN (${ids})
AND ${whereClause}
`;
}
const SQL = `
SELECT
pr.product_id,
COUNT(*) AS review_count,
CAST(AVG(pr.rating) AS DECIMAL(10, 2)) AS average_rating,
SUM(CASE WHEN pr.rating = 1 THEN 1 ELSE 0 END) AS rating_count_1,
SUM(CASE WHEN pr.rating = 2 THEN 1 ELSE 0 END) AS rating_count_2,
SUM(CASE WHEN pr.rating = 3 THEN 1 ELSE 0 END) AS rating_count_3,
SUM(CASE WHEN pr.rating = 4 THEN 1 ELSE 0 END) AS rating_count_4,
SUM(CASE WHEN pr.rating = 5 THEN 1 ELSE 0 END) AS rating_count_5
FROM product_review pr
WHERE ${whereClause}
GROUP BY pr.product_id`;
const productReviewStats = await sharedContext.manager.execute(SQL);
return productReviewStats.map((s) => ({
...s,
average_rating: parseFloat(s.average_rating),
}));
}
}
__decorate([
(0, utils_1.InjectManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array, Object]),
__metadata("design:returntype", Promise)
], ProductReviewService.prototype, "calculateProductReviewStats", null);
exports.default = ProductReviewService;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3Byb2R1Y3QtcmV2aWV3L3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7QUFDQSxxREFJbUM7QUFHbkMscUNBTWtCO0FBY2xCLE1BQU0sZUFBZSxHQUFHO0lBQ3RCLEVBQUUsR0FBRyxFQUFFLGNBQWMsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFO0lBQ3JDLEVBQUUsR0FBRyxFQUFFLHlCQUF5QixFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUU7Q0FDakQsQ0FBQztBQUVGLE1BQU0sb0JBQXFCLFNBQVEsSUFBQSxxQkFBYSxFQUFDO0lBQy9DLGFBQWEsRUFBRSwyQkFBa0I7SUFDakMscUJBQXFCLEVBQUUsbUNBQTBCO0lBQ2pELGtCQUFrQixFQUFFLGdDQUF1QjtJQUMzQyxxQkFBcUIsRUFBRSxtQ0FBMEI7SUFDakQsbUJBQW1CLEVBQUUsaUNBQXdCO0NBQzlDLENBQUM7SUFDQSxZQUFZLFNBQVM7UUFDbkIsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ2pCLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFUyxLQUFLLENBQUMsb0JBQW9CO1FBQ2xDLE1BQU0sZUFBZSxHQUFHLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixFQUFFLENBQUM7UUFFOUQsS0FBSyxNQUFNLGFBQWEsSUFBSSxlQUFlLEVBQUUsQ0FBQztZQUM1QyxNQUFNLFlBQVksR0FBRyxlQUFlLENBQUMsSUFBSSxDQUN2QyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLEdBQUcsS0FBSyxhQUFhLENBQUMsR0FBRyxDQUM3QyxDQUFDO1lBQ0YsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2dCQUNsQixNQUFNLElBQUksQ0FBQywwQkFBMEIsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUN2RCxDQUFDO1FBQ0gs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