@deenruv/reviews-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +58 -0
- package/dist/plugin-server/api/admin-api.resolver.d.ts +67 -0
- package/dist/plugin-server/api/admin-api.resolver.js +127 -0
- package/dist/plugin-server/api/review-asset.resolver.d.ts +11 -0
- package/dist/plugin-server/api/review-asset.resolver.js +37 -0
- package/dist/plugin-server/api/review-customer.resolver.d.ts +15 -0
- package/dist/plugin-server/api/review-customer.resolver.js +52 -0
- package/dist/plugin-server/api/review-order.resolver.d.ts +8 -0
- package/dist/plugin-server/api/review-order.resolver.js +47 -0
- package/dist/plugin-server/api/review-product.resolver.d.ts +15 -0
- package/dist/plugin-server/api/review-product.resolver.js +50 -0
- package/dist/plugin-server/api/shop-api.resolver.d.ts +28 -0
- package/dist/plugin-server/api/shop-api.resolver.js +57 -0
- package/dist/plugin-server/constants.d.ts +6 -0
- package/dist/plugin-server/constants.js +7 -0
- package/dist/plugin-server/entities/review-translation.entity.d.ts +9 -0
- package/dist/plugin-server/entities/review-translation.entity.js +37 -0
- package/dist/plugin-server/entities/review.entity.d.ts +24 -0
- package/dist/plugin-server/entities/review.entity.js +89 -0
- package/dist/plugin-server/events.d.ts +10 -0
- package/dist/plugin-server/events.js +11 -0
- package/dist/plugin-server/extensions/admin-api.extension.d.ts +2 -0
- package/dist/plugin-server/extensions/admin-api.extension.js +70 -0
- package/dist/plugin-server/extensions/shared.extension.d.ts +2 -0
- package/dist/plugin-server/extensions/shared.extension.js +87 -0
- package/dist/plugin-server/extensions/shop-api.extension.d.ts +2 -0
- package/dist/plugin-server/extensions/shop-api.extension.js +60 -0
- package/dist/plugin-server/index.d.ts +15 -0
- package/dist/plugin-server/index.js +86 -0
- package/dist/plugin-server/services/reviews.service.d.ts +85 -0
- package/dist/plugin-server/services/reviews.service.js +366 -0
- package/dist/plugin-server/state/reviews.state.d.ts +16 -0
- package/dist/plugin-server/state/reviews.state.js +63 -0
- package/dist/plugin-server/types.d.ts +33 -0
- package/dist/plugin-server/types.js +1 -0
- package/dist/plugin-server/zeus/const.d.ts +6 -0
- package/dist/plugin-server/zeus/const.js +4027 -0
- package/dist/plugin-server/zeus/index.d.ts +20410 -0
- package/dist/plugin-server/zeus/index.js +443 -0
- package/dist/plugin-ui/components/OrderInfo.d.ts +5 -0
- package/dist/plugin-ui/components/OrderInfo.js +42 -0
- package/dist/plugin-ui/components/ProductInfo.d.ts +5 -0
- package/dist/plugin-ui/components/ProductInfo.js +36 -0
- package/dist/plugin-ui/components/ReviewCustomer.d.ts +2 -0
- package/dist/plugin-ui/components/ReviewCustomer.js +166 -0
- package/dist/plugin-ui/components/ReviewOrder.d.ts +2 -0
- package/dist/plugin-ui/components/ReviewOrder.js +57 -0
- package/dist/plugin-ui/components/ReviewProductSidebar.d.ts +2 -0
- package/dist/plugin-ui/components/ReviewProductSidebar.js +29 -0
- package/dist/plugin-ui/components/ReviewStateChange.d.ts +5 -0
- package/dist/plugin-ui/components/ReviewStateChange.js +35 -0
- package/dist/plugin-ui/components/UniversalSelectDialog.d.ts +16 -0
- package/dist/plugin-ui/components/UniversalSelectDialog.js +35 -0
- package/dist/plugin-ui/components/index.d.ts +2 -0
- package/dist/plugin-ui/components/index.js +2 -0
- package/dist/plugin-ui/constants.d.ts +1 -0
- package/dist/plugin-ui/constants.js +1 -0
- package/dist/plugin-ui/graphql/index.d.ts +3 -0
- package/dist/plugin-ui/graphql/index.js +3 -0
- package/dist/plugin-ui/graphql/mutations.d.ts +35 -0
- package/dist/plugin-ui/graphql/mutations.js +28 -0
- package/dist/plugin-ui/graphql/queries.d.ts +293 -0
- package/dist/plugin-ui/graphql/queries.js +34 -0
- package/dist/plugin-ui/graphql/scalars.d.ts +16 -0
- package/dist/plugin-ui/graphql/scalars.js +14 -0
- package/dist/plugin-ui/graphql/selectors.d.ts +58 -0
- package/dist/plugin-ui/graphql/selectors.js +39 -0
- package/dist/plugin-ui/index.d.ts +7 -0
- package/dist/plugin-ui/index.js +69 -0
- package/dist/plugin-ui/locales/en/index.d.ts +108 -0
- package/dist/plugin-ui/locales/en/index.js +2 -0
- package/dist/plugin-ui/locales/en/reviews.json +107 -0
- package/dist/plugin-ui/locales/pl/index.d.ts +108 -0
- package/dist/plugin-ui/locales/pl/index.js +2 -0
- package/dist/plugin-ui/locales/pl/reviews.json +107 -0
- package/dist/plugin-ui/pages/Review.d.ts +2 -0
- package/dist/plugin-ui/pages/Review.js +263 -0
- package/dist/plugin-ui/pages/Reviews.d.ts +2 -0
- package/dist/plugin-ui/pages/Reviews.js +174 -0
- package/dist/plugin-ui/tsconfig.json +18 -0
- package/dist/plugin-ui/zeus/const.d.ts +6 -0
- package/dist/plugin-ui/zeus/const.js +4027 -0
- package/dist/plugin-ui/zeus/index.d.ts +20410 -0
- package/dist/plugin-ui/zeus/index.js +459 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.d.ts +3 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.js +9 -0
- package/package.json +61 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
14
|
+
var t = {};
|
|
15
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
16
|
+
t[p] = s[p];
|
|
17
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
18
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
19
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
20
|
+
t[p[i]] = s[p[i]];
|
|
21
|
+
}
|
|
22
|
+
return t;
|
|
23
|
+
};
|
|
24
|
+
import { CustomerService, EntityHydrator, EventBus, ListQueryBuilder, OrderService, ProductVariantService, TransactionalConnection, TranslatableSaver, TranslatorService, } from "@deenruv/core";
|
|
25
|
+
import { Inject, Injectable, Logger } from "@nestjs/common";
|
|
26
|
+
import { ReviewStateMachine } from "../state/reviews.state.js";
|
|
27
|
+
import { ReviewEntity } from "../entities/review.entity.js";
|
|
28
|
+
import { REVIEWS_PLUGIN_OPTIONS, ReviewState } from "../constants.js";
|
|
29
|
+
import { ReviewEntityTranslation } from "../entities/review-translation.entity.js";
|
|
30
|
+
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
31
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
32
|
+
import ReviewChangeStateEvent from "../events.js";
|
|
33
|
+
let ReviewsService = class ReviewsService {
|
|
34
|
+
constructor(options, connection, reviewStateMachine, productVariantService, orderService, translatableSaver, translatorService, customerServicer, listQueryBuilder, eventBus, entityHydrator) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.connection = connection;
|
|
37
|
+
this.reviewStateMachine = reviewStateMachine;
|
|
38
|
+
this.productVariantService = productVariantService;
|
|
39
|
+
this.orderService = orderService;
|
|
40
|
+
this.translatableSaver = translatableSaver;
|
|
41
|
+
this.translatorService = translatorService;
|
|
42
|
+
this.customerServicer = customerServicer;
|
|
43
|
+
this.listQueryBuilder = listQueryBuilder;
|
|
44
|
+
this.eventBus = eventBus;
|
|
45
|
+
this.entityHydrator = entityHydrator;
|
|
46
|
+
this.logger = new Logger("ReviewsService");
|
|
47
|
+
this.error = this.logger.error.bind(this.logger);
|
|
48
|
+
this.log = this.logger.log.bind(this.logger);
|
|
49
|
+
this.translateStrategy = options.translateStrategy;
|
|
50
|
+
if (!this.translateStrategy)
|
|
51
|
+
this.canTranslate = false;
|
|
52
|
+
else
|
|
53
|
+
this.canTranslate = true;
|
|
54
|
+
}
|
|
55
|
+
async hasShopReviewCreated(ctx, customer) {
|
|
56
|
+
const qb = this.connection
|
|
57
|
+
.getRepository(ctx, ReviewEntity)
|
|
58
|
+
.createQueryBuilder("review");
|
|
59
|
+
qb.select("COUNT(review.id)", "count")
|
|
60
|
+
.where("review.authorId = :authorId", { authorId: customer.id })
|
|
61
|
+
.andWhere("review.productId IS NULL")
|
|
62
|
+
.andWhere("review.orderId IS NULL");
|
|
63
|
+
const result = await qb.getRawOne();
|
|
64
|
+
if (!result)
|
|
65
|
+
return false;
|
|
66
|
+
const count = parseInt(result.count, 10);
|
|
67
|
+
if (isNaN(count))
|
|
68
|
+
return false;
|
|
69
|
+
return count > 0;
|
|
70
|
+
}
|
|
71
|
+
async getAverageRatings(ctx) {
|
|
72
|
+
var _a, _b, _c, _d;
|
|
73
|
+
const reviewRepo = this.connection.getRepository(ctx, ReviewEntity);
|
|
74
|
+
const productQb = reviewRepo.createQueryBuilder("review");
|
|
75
|
+
productQb
|
|
76
|
+
.select("SUM(review.rating)", "total")
|
|
77
|
+
.addSelect("COUNT(review.rating)", "count")
|
|
78
|
+
.where("review.productId IS NOT NULL")
|
|
79
|
+
.andWhere("review.state = :state", { state: ReviewState.ACCEPTED });
|
|
80
|
+
const productResult = await productQb.getRawOne();
|
|
81
|
+
const shopQb = reviewRepo.createQueryBuilder("review");
|
|
82
|
+
shopQb
|
|
83
|
+
.select("SUM(review.rating)", "total")
|
|
84
|
+
.addSelect("COUNT(review.rating)", "count")
|
|
85
|
+
.where("review.productId IS NULL")
|
|
86
|
+
.andWhere("review.state = :state", { state: ReviewState.ACCEPTED });
|
|
87
|
+
const shopResult = await shopQb.getRawOne();
|
|
88
|
+
const productsAverageRating = {
|
|
89
|
+
total: parseInt((_a = productResult === null || productResult === void 0 ? void 0 : productResult.total) !== null && _a !== void 0 ? _a : "0", 10),
|
|
90
|
+
count: parseInt((_b = productResult === null || productResult === void 0 ? void 0 : productResult.count) !== null && _b !== void 0 ? _b : "0", 10),
|
|
91
|
+
};
|
|
92
|
+
const shopAverageRating = {
|
|
93
|
+
total: parseInt((_c = shopResult === null || shopResult === void 0 ? void 0 : shopResult.total) !== null && _c !== void 0 ? _c : "0", 10),
|
|
94
|
+
count: parseInt((_d = shopResult === null || shopResult === void 0 ? void 0 : shopResult.count) !== null && _d !== void 0 ? _d : "0", 10),
|
|
95
|
+
};
|
|
96
|
+
return { shopAverageRating, productsAverageRating };
|
|
97
|
+
}
|
|
98
|
+
async getOrderReview(ctx, order, relations) {
|
|
99
|
+
const review = await this.connection
|
|
100
|
+
.getRepository(ctx, ReviewEntity)
|
|
101
|
+
.findOne({ where: { order: { id: order.id } }, relations });
|
|
102
|
+
if (!review)
|
|
103
|
+
return null;
|
|
104
|
+
return this.translatorService.translate(review, ctx);
|
|
105
|
+
}
|
|
106
|
+
async isOrderReviewed(ctx, order) {
|
|
107
|
+
const qb = this.connection
|
|
108
|
+
.getRepository(ctx, ReviewEntity)
|
|
109
|
+
.createQueryBuilder("review");
|
|
110
|
+
qb.select("COUNT(review.id)", "count").where("review.orderId = :orderId", {
|
|
111
|
+
orderId: order.id,
|
|
112
|
+
});
|
|
113
|
+
const result = await qb.getRawOne();
|
|
114
|
+
if (!result)
|
|
115
|
+
return false;
|
|
116
|
+
const count = parseInt(result.count, 10);
|
|
117
|
+
if (isNaN(count))
|
|
118
|
+
return false;
|
|
119
|
+
return count > 0;
|
|
120
|
+
}
|
|
121
|
+
async getAverageRating(ctx, product) {
|
|
122
|
+
var _a;
|
|
123
|
+
const productId = product.id;
|
|
124
|
+
const qb = this.connection
|
|
125
|
+
.getRepository(ctx, ReviewEntity)
|
|
126
|
+
.createQueryBuilder("review");
|
|
127
|
+
qb.select("AVG(review.rating)", "averageRating")
|
|
128
|
+
.where("review.productId = :productId", { productId })
|
|
129
|
+
.andWhere("review.state = :state", { state: ReviewState.ACCEPTED });
|
|
130
|
+
const result = await qb.getRawOne();
|
|
131
|
+
const averageRating = (_a = result === null || result === void 0 ? void 0 : result.averageRating) !== null && _a !== void 0 ? _a : 0;
|
|
132
|
+
const value = parseFloat(averageRating);
|
|
133
|
+
return isNaN(value) ? 0 : value;
|
|
134
|
+
}
|
|
135
|
+
async translateReviews(ctx, input) {
|
|
136
|
+
if (!this.canTranslate || !this.translateStrategy) {
|
|
137
|
+
this.error("ReviewsService.translateReviews called, but translateStrategy is not set");
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const { id, languages } = input;
|
|
141
|
+
const review = await this.connection.getEntityOrThrow(ctx, ReviewEntity, id, { relations: ["translations"] });
|
|
142
|
+
if (!review) {
|
|
143
|
+
this.error(`Review with id ${id} not found`);
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
return this.translateStrategy.translateReviews(ctx, review, languages);
|
|
147
|
+
}
|
|
148
|
+
async updateTranslationsReview(ctx, input) {
|
|
149
|
+
const { id, translations } = input;
|
|
150
|
+
const review = await this.connection.getEntityOrThrow(ctx, ReviewEntity, id, { relations: ["translations"] });
|
|
151
|
+
if (!review) {
|
|
152
|
+
this.error(`Review with id ${id} not found`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const existingTranslations = review.translations || [];
|
|
156
|
+
const updatedTranslations = existingTranslations.map((translation) => {
|
|
157
|
+
const newTranslation = translations.find((t) => t.languageCode === translation.languageCode);
|
|
158
|
+
if (newTranslation) {
|
|
159
|
+
return Object.assign(Object.assign({}, translation), { body: newTranslation.body });
|
|
160
|
+
}
|
|
161
|
+
return translation;
|
|
162
|
+
});
|
|
163
|
+
const newTranslations = translations
|
|
164
|
+
.filter((t) => !existingTranslations.some((et) => et.languageCode === t.languageCode))
|
|
165
|
+
.map((t) => ({
|
|
166
|
+
languageCode: t.languageCode,
|
|
167
|
+
body: t.body,
|
|
168
|
+
}));
|
|
169
|
+
const allTranslations = [...updatedTranslations, ...newTranslations];
|
|
170
|
+
if (allTranslations.length === 0) {
|
|
171
|
+
this.error(`No translations provided for review with id ${id}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
return this.translatableSaver.update({
|
|
175
|
+
ctx,
|
|
176
|
+
entityType: ReviewEntity,
|
|
177
|
+
translationType: ReviewEntityTranslation,
|
|
178
|
+
input: { id, translations: allTranslations },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async getReviewInfoForProduct(ctx, productId) {
|
|
182
|
+
const qb = this.connection
|
|
183
|
+
.getRepository(ctx, ReviewEntity)
|
|
184
|
+
.createQueryBuilder("review");
|
|
185
|
+
qb.select("AVG(review.rating)", "averageRating")
|
|
186
|
+
.addSelect("COUNT(review.id)", "totalReviews")
|
|
187
|
+
.addSelect("SUM(review.rating)", "totalRatings")
|
|
188
|
+
.where("review.productId = :productId", { productId })
|
|
189
|
+
.andWhere("review.state = :state", { state: ReviewState.ACCEPTED });
|
|
190
|
+
const result = await qb.getRawOne();
|
|
191
|
+
if (!result) {
|
|
192
|
+
return { averageRating: 0, totalReviews: 0, totalRatings: 0 };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
averageRating: parseFloat(result.averageRating) || 0,
|
|
196
|
+
totalReviews: parseInt(result.totalReviews, 10) || 0,
|
|
197
|
+
totalRatings: parseInt(result.totalRatings, 10) || 0,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async getReviewsConfig(ctx) {
|
|
201
|
+
const configFn = this.options.getReviewsConfig;
|
|
202
|
+
const emptyConfig = {
|
|
203
|
+
reviewsLanguages: [],
|
|
204
|
+
canTranslate: this.canTranslate,
|
|
205
|
+
};
|
|
206
|
+
if (typeof configFn !== "function") {
|
|
207
|
+
this.error("ReviewsPluginOptions.getReviewsConfig is not set or not a function, returning empty config");
|
|
208
|
+
return emptyConfig;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const response = await configFn(ctx);
|
|
212
|
+
return Object.assign(Object.assign({}, response), { canTranslate: this.canTranslate });
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
this.error(`Error while executing ReviewsPluginOptions.getReviewsConfig: ${e instanceof Error ? e.message : "Unknown error"}`);
|
|
216
|
+
return emptyConfig;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async getReview(ctx, id, relations) {
|
|
220
|
+
const review = await this.connection.getEntityOrThrow(ctx, ReviewEntity, id, { relations });
|
|
221
|
+
return this.translatorService.translate(review, ctx);
|
|
222
|
+
}
|
|
223
|
+
async changeReviewState(ctx, input) {
|
|
224
|
+
const response = [];
|
|
225
|
+
for (let _a of input) {
|
|
226
|
+
const { id } = _a, rest = __rest(_a, ["id"]);
|
|
227
|
+
const review = await this.connection.getEntityOrThrow(ctx, ReviewEntity, id);
|
|
228
|
+
const { finalize } = await this.reviewStateMachine.transition(ctx, review, rest);
|
|
229
|
+
await this.connection.getRepository(ctx, ReviewEntity).save(review);
|
|
230
|
+
await finalize();
|
|
231
|
+
await this.eventBus.publish(new ReviewChangeStateEvent(ctx, review, review.state, rest.state, rest.message));
|
|
232
|
+
response.push({ id, success: true });
|
|
233
|
+
}
|
|
234
|
+
return response.length === 1 ? response[0] : response;
|
|
235
|
+
}
|
|
236
|
+
async listReviews(ctx, _options, relations, showAll = false) {
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
+
const options = (_options || {});
|
|
239
|
+
const where = ctx.apiType !== "admin"
|
|
240
|
+
? showAll
|
|
241
|
+
? {}
|
|
242
|
+
: { state: ReviewState.ACCEPTED }
|
|
243
|
+
: {};
|
|
244
|
+
const qb = this.listQueryBuilder.build(ReviewEntity, options, {
|
|
245
|
+
ctx,
|
|
246
|
+
where,
|
|
247
|
+
relations,
|
|
248
|
+
customPropertyMap: {
|
|
249
|
+
productId: "product.id",
|
|
250
|
+
orderId: "order.id",
|
|
251
|
+
customerId: "author.id",
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
qb.addOrderBy("reviewentity.createdAt", "DESC");
|
|
255
|
+
qb.leftJoin("reviewentity.product", "product");
|
|
256
|
+
qb.leftJoin("product.channels", "productChannel");
|
|
257
|
+
qb.leftJoin("reviewentity.order", "order");
|
|
258
|
+
qb.leftJoin("order.channels", "orderChannel");
|
|
259
|
+
qb.andWhere(`(
|
|
260
|
+
(product.id IS NOT NULL AND productChannel.id = :channelId)
|
|
261
|
+
OR
|
|
262
|
+
(order.id IS NOT NULL AND orderChannel.id = :channelId)
|
|
263
|
+
OR
|
|
264
|
+
(product.id IS NULL AND order.id IS NULL)
|
|
265
|
+
)`, { channelId: ctx.channelId });
|
|
266
|
+
const [items, totalItems] = await qb.getManyAndCount();
|
|
267
|
+
const reviews = items.map((item) => this.translatorService.translate(item, ctx));
|
|
268
|
+
return { items: reviews, totalItems };
|
|
269
|
+
}
|
|
270
|
+
async createReview(ctx, input) {
|
|
271
|
+
const { productVariantId, orderId, rating, uploadedAssets, authorLocation, authorName, authorEmailAddress, keepAnonymous } = input, translations = __rest(input, ["productVariantId", "orderId", "rating", "uploadedAssets", "authorLocation", "authorName", "authorEmailAddress", "keepAnonymous"]);
|
|
272
|
+
let variant = undefined;
|
|
273
|
+
if (productVariantId) {
|
|
274
|
+
variant = await this.productVariantService.findOne(ctx, productVariantId, ["product"]);
|
|
275
|
+
}
|
|
276
|
+
return this.translatableSaver.create({
|
|
277
|
+
ctx,
|
|
278
|
+
entityType: ReviewEntity,
|
|
279
|
+
translationType: ReviewEntityTranslation,
|
|
280
|
+
input: {
|
|
281
|
+
translations: [Object.assign({ languageCode: ctx.languageCode }, translations)],
|
|
282
|
+
},
|
|
283
|
+
beforeSave: async (review) => {
|
|
284
|
+
review.authorName = authorName || "";
|
|
285
|
+
review.authorLocation = authorLocation || "";
|
|
286
|
+
review.authorEmailAddress = authorEmailAddress || "";
|
|
287
|
+
review.keepAnonymous = keepAnonymous || false;
|
|
288
|
+
review.rating = rating;
|
|
289
|
+
if (variant) {
|
|
290
|
+
review.productVariant = variant;
|
|
291
|
+
if (variant.product)
|
|
292
|
+
review.product = variant.product;
|
|
293
|
+
}
|
|
294
|
+
if (orderId) {
|
|
295
|
+
const order = await this.orderService.findOne(ctx, orderId, []);
|
|
296
|
+
if (order) {
|
|
297
|
+
review.order = order;
|
|
298
|
+
if (order.customer)
|
|
299
|
+
review.author = order.customer;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (uploadedAssets) {
|
|
303
|
+
const filteredAssets = uploadedAssets.filter((asset) => !!asset && asset.length > 0);
|
|
304
|
+
review.assetKeys = filteredAssets;
|
|
305
|
+
}
|
|
306
|
+
if (ctx.activeUserId && !review.author) {
|
|
307
|
+
// If the review is being created by an authenticated user, we can set the author
|
|
308
|
+
const customer = await this.customerServicer.findOneByUserId(ctx, ctx.activeUserId);
|
|
309
|
+
if (customer) {
|
|
310
|
+
await this.entityHydrator.hydrate(ctx, customer, {
|
|
311
|
+
relations: ["addresses"],
|
|
312
|
+
});
|
|
313
|
+
review.author = customer;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async getReviewsStorage(ctx, input) {
|
|
320
|
+
const { bucket, client, folder } = this.options.s3;
|
|
321
|
+
const results = [];
|
|
322
|
+
for (const { filename } of input) {
|
|
323
|
+
const timestamp = new Date().getTime();
|
|
324
|
+
const [name, ext] = filename.split(".");
|
|
325
|
+
const hash = [
|
|
326
|
+
timestamp,
|
|
327
|
+
name.replace(/[^a-z0-9]/gi, "_").toLowerCase(),
|
|
328
|
+
ext,
|
|
329
|
+
].join("-");
|
|
330
|
+
const key = [folder, hash].join("/");
|
|
331
|
+
const command = new PutObjectCommand({ Bucket: bucket, Key: key });
|
|
332
|
+
const url = await getSignedUrl(client, command);
|
|
333
|
+
results.push({ key, url });
|
|
334
|
+
}
|
|
335
|
+
return results;
|
|
336
|
+
}
|
|
337
|
+
async getReviewAssets(ctx, review) {
|
|
338
|
+
const keys = review.assetKeys || [];
|
|
339
|
+
if (keys.length > 0) {
|
|
340
|
+
const { bucket, client } = this.options.s3;
|
|
341
|
+
const results = [];
|
|
342
|
+
for (const key of keys) {
|
|
343
|
+
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
|
|
344
|
+
const url = await getSignedUrl(client, command);
|
|
345
|
+
results.push({ key, url });
|
|
346
|
+
}
|
|
347
|
+
return results;
|
|
348
|
+
}
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
ReviewsService = __decorate([
|
|
353
|
+
Injectable(),
|
|
354
|
+
__param(0, Inject(REVIEWS_PLUGIN_OPTIONS)),
|
|
355
|
+
__metadata("design:paramtypes", [Object, TransactionalConnection,
|
|
356
|
+
ReviewStateMachine,
|
|
357
|
+
ProductVariantService,
|
|
358
|
+
OrderService,
|
|
359
|
+
TranslatableSaver,
|
|
360
|
+
TranslatorService,
|
|
361
|
+
CustomerService,
|
|
362
|
+
ListQueryBuilder,
|
|
363
|
+
EventBus,
|
|
364
|
+
EntityHydrator])
|
|
365
|
+
], ReviewsService);
|
|
366
|
+
export { ReviewsService };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RequestContext } from "@deenruv/core";
|
|
2
|
+
import { ReviewState } from "../constants.js";
|
|
3
|
+
import { ReviewEntity } from "../entities/review.entity.js";
|
|
4
|
+
type ChangeReviewStateInput = {
|
|
5
|
+
state: ReviewState;
|
|
6
|
+
message?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class ReviewStateMachine {
|
|
9
|
+
constructor();
|
|
10
|
+
private readonly config;
|
|
11
|
+
getNextStates(review: ReviewEntity): readonly ReviewState[];
|
|
12
|
+
transition(ctx: RequestContext, review: ReviewEntity, { state, ...rest }: ChangeReviewStateInput): Promise<{
|
|
13
|
+
finalize: () => Promise<any>;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import { Injectable } from "@nestjs/common";
|
|
22
|
+
import { FSM, } from "@deenruv/core";
|
|
23
|
+
import { ReviewState } from "../constants.js";
|
|
24
|
+
const transitions = {
|
|
25
|
+
ACCEPTED: { to: [ReviewState.DECLINED] },
|
|
26
|
+
DECLINED: { to: [ReviewState.ACCEPTED] },
|
|
27
|
+
PENDING: { to: [ReviewState.ACCEPTED, ReviewState.DECLINED] },
|
|
28
|
+
};
|
|
29
|
+
let ReviewStateMachine = class ReviewStateMachine {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.config = {
|
|
32
|
+
transitions,
|
|
33
|
+
onTransitionStart: async (fromState, toState, { ctx, review, message }) => {
|
|
34
|
+
if ([ReviewState.ACCEPTED, ReviewState.DECLINED].includes(toState)) {
|
|
35
|
+
review.responseCreatedAt = new Date();
|
|
36
|
+
review.response = message;
|
|
37
|
+
if (ctx.activeUserId) {
|
|
38
|
+
// we need only id to make proper relation
|
|
39
|
+
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
review.responseAuthor = { id: ctx.activeUserId };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
getNextStates(review) {
|
|
47
|
+
const fsm = new FSM(this.config, review.state);
|
|
48
|
+
return fsm.getNextStates();
|
|
49
|
+
}
|
|
50
|
+
async transition(ctx, review, _a) {
|
|
51
|
+
var { state } = _a, rest = __rest(_a, ["state"]);
|
|
52
|
+
const fsm = new FSM(this.config, review.state);
|
|
53
|
+
const result = await fsm.transitionTo(state, Object.assign({ ctx,
|
|
54
|
+
review }, rest));
|
|
55
|
+
review.state = state;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
ReviewStateMachine = __decorate([
|
|
60
|
+
Injectable(),
|
|
61
|
+
__metadata("design:paramtypes", [])
|
|
62
|
+
], ReviewStateMachine);
|
|
63
|
+
export { ReviewStateMachine };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
2
|
+
import { ID, InjectableStrategy, RequestContext } from "@deenruv/core";
|
|
3
|
+
import { ReviewEntity } from "./entities/review.entity.js";
|
|
4
|
+
import { LanguageCode } from "./zeus/index.js";
|
|
5
|
+
export interface TranslateReviewStrategy extends InjectableStrategy {
|
|
6
|
+
translateReviews: (ctx: RequestContext, review: ReviewEntity, languageCodes: LanguageCode[]) => Promise<Array<{
|
|
7
|
+
languageCode: LanguageCode;
|
|
8
|
+
body: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
}>>;
|
|
11
|
+
}
|
|
12
|
+
export type ReviewsPluginOptions = {
|
|
13
|
+
s3: {
|
|
14
|
+
bucket: string;
|
|
15
|
+
client: S3Client;
|
|
16
|
+
folder: string;
|
|
17
|
+
};
|
|
18
|
+
getReviewsConfig: (ctx: RequestContext) => Promise<{
|
|
19
|
+
reviewsLanguages: string[];
|
|
20
|
+
}>;
|
|
21
|
+
translateStrategy?: TranslateReviewStrategy;
|
|
22
|
+
};
|
|
23
|
+
export type CreateReviewInput = {
|
|
24
|
+
body: string;
|
|
25
|
+
rating: number;
|
|
26
|
+
keepAnonymous: boolean;
|
|
27
|
+
orderId?: ID;
|
|
28
|
+
productVariantId?: ID;
|
|
29
|
+
uploadedAssets?: string[];
|
|
30
|
+
authorName?: string;
|
|
31
|
+
authorLocation?: string;
|
|
32
|
+
authorEmailAddress?: string;
|
|
33
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|