reviews-plugin.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { OnApplicationBootstrap } from '@nestjs/common';
  2. import {
  3. LanguageCode,
  4. PluginCommonModule,
  5. Product,
  6. ProductVariant,
  7. RequestContextService,
  8. TransactionalConnection,
  9. TranslatableSaver,
  10. VendurePlugin,
  11. } from '@vendure/core';
  12. import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
  13. import path from 'path';
  14. import { adminApiExtensions, shopApiExtensions } from './api/api-extensions';
  15. import { ProductEntityResolver } from './api/product-entity.resolver';
  16. import { ProductReviewAdminResolver } from './api/product-review-admin.resolver';
  17. import { ProductReviewEntityResolver } from './api/product-review-entity.resolver';
  18. import { ProductReviewShopResolver } from './api/product-review-shop.resolver';
  19. import { ProductReviewTranslation } from './entities/product-review-translation.entity';
  20. import { ProductReview } from './entities/product-review.entity';
  21. @VendurePlugin({
  22. imports: [PluginCommonModule],
  23. entities: [ProductReview, ProductReviewTranslation],
  24. adminApiExtensions: {
  25. schema: adminApiExtensions,
  26. resolvers: [ProductEntityResolver, ProductReviewAdminResolver, ProductReviewEntityResolver],
  27. },
  28. shopApiExtensions: {
  29. schema: shopApiExtensions,
  30. resolvers: [ProductEntityResolver, ProductReviewShopResolver, ProductReviewEntityResolver],
  31. },
  32. configuration: config => {
  33. config.customFields.Product.push({
  34. name: 'reviewRating',
  35. label: [{ languageCode: LanguageCode.en, value: 'Review rating' }],
  36. public: true,
  37. nullable: true,
  38. type: 'float',
  39. ui: { tab: 'Reviews', component: 'star-rating-form-input' },
  40. });
  41. config.customFields.Product.push({
  42. name: 'reviewCount',
  43. label: [{ languageCode: LanguageCode.en, value: 'Review count' }],
  44. public: true,
  45. defaultValue: 0,
  46. type: 'float',
  47. ui: { tab: 'Reviews', component: 'review-count-link' },
  48. });
  49. config.customFields.Product.push({
  50. name: 'featuredReview',
  51. label: [{ languageCode: LanguageCode.en, value: 'Featured review' }],
  52. public: true,
  53. type: 'relation',
  54. entity: ProductReview,
  55. ui: { tab: 'Reviews', fullWidth: true, component: 'review-single-select' },
  56. inverseSide: undefined,
  57. });
  58. config.customFields.Product.push({
  59. name: 'promotedReviews',
  60. label: [{ languageCode: LanguageCode.en, value: 'Promoted Reviews' }],
  61. public: true,
  62. type: 'relation',
  63. list: true,
  64. entity: ProductReview,
  65. ui: { tab: 'Reviews', fullWidth: true, component: 'review-multi-select' },
  66. });
  67. config.customFields.Product.push({
  68. name: 'translatableText',
  69. label: [{ languageCode: LanguageCode.en, value: 'Translatable text' }],
  70. public: true,
  71. type: 'localeText',
  72. ui: { tab: 'Reviews' },
  73. });
  74. config.customFields.ProductReview = [
  75. {
  76. type: 'localeText',
  77. name: 'reviewerName',
  78. label: [{ languageCode: LanguageCode.en, value: 'Reviewer name' }],
  79. public: true,
  80. nullable: true,
  81. ui: { component: 'textarea' },
  82. },
  83. ];
  84. config.customFields.ProductReview.push({
  85. name: 'verifiedReviewerName',
  86. label: [{ languageCode: LanguageCode.en, value: 'Verified reviewer name' }],
  87. public: true,
  88. nullable: true,
  89. type: 'string',
  90. readonly: true,
  91. });
  92. return config;
  93. },
  94. dashboard: './dashboard/index.tsx',
  95. })
  96. export class ReviewsPlugin implements OnApplicationBootstrap {
  97. constructor(
  98. private readonly connection: TransactionalConnection,
  99. private readonly requestContextService: RequestContextService,
  100. private readonly translatableSaver: TranslatableSaver,
  101. ) {}
  102. static uiExtensions: AdminUiExtension = {
  103. extensionPath: path.join(__dirname, 'ui'),
  104. routes: [{ route: 'product-reviews', filePath: 'routes.ts' }],
  105. providers: ['providers.ts'],
  106. };
  107. async onApplicationBootstrap() {
  108. const ctx = await this.requestContextService.create({
  109. apiType: 'admin',
  110. languageCode: LanguageCode.en,
  111. });
  112. const reviewCount = await this.connection.getRepository(ctx, ProductReview).count();
  113. if (reviewCount === 0) {
  114. const products = await this.connection.getRepository(ctx, Product).find();
  115. if (products.length === 0) {
  116. return;
  117. }
  118. const demoReviews = [
  119. {
  120. summary: 'Great product, highly recommend!',
  121. body: 'I was really impressed with the quality and performance. Would definitely buy again.',
  122. rating: 5,
  123. authorName: 'John Smith',
  124. authorLocation: 'New York, USA',
  125. state: 'approved',
  126. translations: [
  127. {
  128. languageCode: LanguageCode.en,
  129. text: 'Great product, highly recommend!',
  130. customFields: {
  131. reviewerName: 'JSmith123',
  132. },
  133. },
  134. ],
  135. customFields: {
  136. verifiedReviewerName: 'JSmith123 verified',
  137. },
  138. },
  139. {
  140. summary: 'Good value for money',
  141. body: 'Does exactly what it says. No complaints.',
  142. rating: 4,
  143. authorName: 'Sarah Wilson',
  144. authorLocation: 'London, UK',
  145. state: 'approved',
  146. translations: [
  147. {
  148. languageCode: LanguageCode.en,
  149. text: 'Good value for money',
  150. customFields: {
  151. reviewerName: 'SarahW',
  152. },
  153. },
  154. ],
  155. customFields: {
  156. verifiedReviewerName: 'SarahW verified',
  157. },
  158. },
  159. {
  160. summary: 'Decent but could be better',
  161. body: 'The product is okay but there is room for improvement in terms of durability.',
  162. rating: 3,
  163. authorName: 'Mike Johnson',
  164. authorLocation: 'Toronto, Canada',
  165. state: 'approved',
  166. translations: [
  167. {
  168. languageCode: LanguageCode.en,
  169. text: 'Decent but could be better',
  170. customFields: {
  171. reviewerName: 'MikeJ',
  172. },
  173. },
  174. ],
  175. customFields: {
  176. verifiedReviewerName: 'MikeJ verified',
  177. },
  178. },
  179. {
  180. summary: 'Exceeded expectations',
  181. body: 'Really happy with this purchase. The quality is outstanding.',
  182. rating: 5,
  183. authorName: 'Emma Brown',
  184. authorLocation: 'Sydney, Australia',
  185. state: 'approved',
  186. translations: [
  187. {
  188. languageCode: LanguageCode.en,
  189. text: 'Exceeded expectations',
  190. customFields: {
  191. reviewerName: 'EmmaB',
  192. },
  193. },
  194. ],
  195. customFields: {
  196. verifiedReviewerName: 'EmmaB verified',
  197. },
  198. },
  199. {
  200. summary: 'Good product, fast delivery',
  201. body: 'Product arrived quickly and works as described. Happy with the purchase.',
  202. rating: 4,
  203. authorName: 'David Lee',
  204. authorLocation: 'Singapore',
  205. state: 'approved',
  206. translations: [
  207. {
  208. languageCode: LanguageCode.en,
  209. text: 'Good product, fast delivery',
  210. customFields: {
  211. reviewerName: 'DavidL',
  212. },
  213. },
  214. ],
  215. customFields: {
  216. verifiedReviewerName: 'DavidL verified',
  217. },
  218. },
  219. ];
  220. for (const review of demoReviews) {
  221. const randomProduct = products[Math.floor(Math.random() * products.length)];
  222. const productVariants = await this.connection.getRepository(ctx, ProductVariant).find({
  223. where: { productId: randomProduct.id },
  224. });
  225. const randomVariant = productVariants[Math.floor(Math.random() * productVariants.length)];
  226. const input = {
  227. ...review,
  228. product: randomProduct,
  229. productVariant: randomVariant,
  230. };
  231. const createdReview = await this.translatableSaver.create({
  232. ctx,
  233. input,
  234. entityType: ProductReview,
  235. translationType: ProductReviewTranslation,
  236. });
  237. }
  238. }
  239. }
  240. }