├── .gitattributes ├── lib ├── index.d.ts ├── plugin │ ├── index.d.ts │ ├── product-recommendation.entity.d.ts │ ├── product-recommendations.service.d.ts │ ├── product-recommendations.resolver.d.ts │ ├── product-recommendation.entity.js │ ├── product-recommendations.service.js │ ├── index.js │ └── product-recommendations.resolver.js ├── index.js ├── package.json └── README.md ├── src ├── index.ts └── plugin │ ├── product-recommendation.entity.ts │ ├── product-recommendations.service.ts │ ├── index.ts │ └── product-recommendations.resolver.ts ├── tsconfig.json ├── package.json ├── LICENSE ├── .gitignore └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { AdminUiExtension } from "@vendure/ui-devkit/compiler"; 2 | export { ProductRecommendationsPlugin } from "./plugin"; 3 | export declare const ProductRecommendationsInputModule: AdminUiExtension["ngModules"][0]; 4 | -------------------------------------------------------------------------------- /lib/plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ID } from "@vendure/core"; 2 | import { RecommendationType } from "./product-recommendation.entity"; 3 | export declare type ProductRecommendationInput = { 4 | product: ID; 5 | recommendation: ID; 6 | type: RecommendationType; 7 | }; 8 | export declare class ProductRecommendationsPlugin { 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AdminUiExtension } from "@vendure/ui-devkit/compiler"; 2 | 3 | export { ProductRecommendationsPlugin } from "./plugin"; 4 | 5 | export const ProductRecommendationsInputModule: AdminUiExtension["ngModules"][0] = { 6 | type: "shared", 7 | ngModuleFileName: "product-recommendations-input.module.ts", 8 | ngModuleName: "ProductRecommendationsInputModule", 9 | }; 10 | -------------------------------------------------------------------------------- /lib/plugin/product-recommendation.entity.d.ts: -------------------------------------------------------------------------------- 1 | import { DeepPartial } from "@vendure/common/lib/shared-types"; 2 | import { VendureEntity, Product } from "@vendure/core"; 3 | export declare enum RecommendationType { 4 | CROSSSELL = "CROSSSELL", 5 | UPSELL = "UPSELL" 6 | } 7 | export declare class ProductRecommendation extends VendureEntity { 8 | constructor(input?: DeepPartial); 9 | product: Product; 10 | recommendation: Product; 11 | type: RecommendationType; 12 | } 13 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ProductRecommendationsInputModule = exports.ProductRecommendationsPlugin = void 0; 4 | var plugin_1 = require("./plugin"); 5 | Object.defineProperty(exports, "ProductRecommendationsPlugin", { enumerable: true, get: function () { return plugin_1.ProductRecommendationsPlugin; } }); 6 | exports.ProductRecommendationsInputModule = { 7 | type: "shared", 8 | ngModuleFileName: "product-recommendations-input.module.ts", 9 | ngModuleName: "ProductRecommendationsInputModule", 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "strict": true, 10 | "sourceMap": false, 11 | "baseUrl": "./", 12 | "strictPropertyInitialization": false, 13 | "removeComments": false, 14 | "outDir": "./lib", 15 | "rootDirs": [ 16 | "./src" 17 | ], 18 | "declaration": true, 19 | "paths": {}, 20 | "allowSyntheticDefaultImports": true 21 | }, 22 | "files": [ 23 | "./src/index.ts", 24 | "./src/plugin/index.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } -------------------------------------------------------------------------------- /lib/plugin/product-recommendations.service.d.ts: -------------------------------------------------------------------------------- 1 | import { FindManyOptions } from "typeorm/find-options/FindManyOptions"; 2 | import { ID, TransactionalConnection } from "@vendure/core"; 3 | import { DeletionResponse } from "@vendure/common/lib/generated-types"; 4 | import { ProductRecommendation } from "./product-recommendation.entity"; 5 | import { ProductRecommendationInput } from "./index"; 6 | export declare class ProductRecommendationService { 7 | private connection; 8 | constructor(connection: TransactionalConnection); 9 | findAll(options: FindManyOptions | undefined): Promise; 10 | findOne(recommendationId: ID): Promise; 11 | create(input: ProductRecommendationInput): Promise; 12 | delete(ids: ID[]): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vendure-product-recommendations", 3 | "version": "0.1.22", 4 | "main": "./index.js", 5 | "description": "Adds product recommendations to vendure", 6 | "repository": "https://github.com/Tyratox/vendure-product-recommendations", 7 | "author": "Nico Hauser", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rimraf lib && tsc && cp package.json ./lib/ && cp README.md ./lib/" 11 | }, 12 | "devDependencies": { 13 | "@apollo/gateway": "0.26.0", 14 | "@types/prosemirror-menu": "^1.0.1", 15 | "@types/prosemirror-model": "^1.7.2", 16 | "@types/prosemirror-state": "^1.2.3", 17 | "@types/prosemirror-view": "^1.11.4", 18 | "@vendure/admin-ui-plugin": "^1.2.0", 19 | "@vendure/common": "^1.2.0", 20 | "@vendure/core": "^1.2.0", 21 | "@vendure/ui-devkit": "^1.2.0", 22 | "rimraf": "^3.0.2", 23 | "typescript": "^4.4.2" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.3.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vendure-product-recommendations", 3 | "version": "0.1.22", 4 | "main": "./index.js", 5 | "description": "Adds product recommendations to vendure", 6 | "repository": "https://github.com/Tyratox/vendure-product-recommendations", 7 | "author": "Nico Hauser", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rimraf lib && tsc && cp package.json ./lib/ && cp README.md ./lib/" 11 | }, 12 | "devDependencies": { 13 | "@apollo/gateway": "0.26.0", 14 | "@types/prosemirror-menu": "^1.0.1", 15 | "@types/prosemirror-model": "^1.7.2", 16 | "@types/prosemirror-state": "^1.2.3", 17 | "@types/prosemirror-view": "^1.11.4", 18 | "@vendure/admin-ui-plugin": "^1.2.0", 19 | "@vendure/common": "^1.2.0", 20 | "@vendure/core": "^1.2.0", 21 | "@vendure/ui-devkit": "^1.2.0", 22 | "rimraf": "^3.0.2", 23 | "typescript": "^4.4.2" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.3.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/plugin/product-recommendation.entity.ts: -------------------------------------------------------------------------------- 1 | import { DeepPartial } from "@vendure/common/lib/shared-types"; 2 | import { VendureEntity, Product } from "@vendure/core"; 3 | import { Column, Entity, ManyToOne, JoinColumn } from "typeorm"; 4 | 5 | export enum RecommendationType { 6 | CROSSSELL = "CROSSSELL", 7 | UPSELL = "UPSELL", 8 | } 9 | 10 | @Entity() 11 | export class ProductRecommendation extends VendureEntity { 12 | constructor(input?: DeepPartial) { 13 | super(input); 14 | } 15 | 16 | @ManyToOne((type) => Product, { 17 | onDelete: "CASCADE", 18 | nullable: false, 19 | eager: true, 20 | }) 21 | @JoinColumn() 22 | product: Product; 23 | 24 | @ManyToOne((type) => Product, { 25 | onDelete: "CASCADE", 26 | nullable: false, 27 | eager: true, 28 | }) 29 | @JoinColumn() 30 | recommendation: Product; 31 | 32 | @Column({ 33 | type: "enum", 34 | enum: RecommendationType, 35 | }) 36 | type: RecommendationType; 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nico Hauser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/plugin/product-recommendations.resolver.d.ts: -------------------------------------------------------------------------------- 1 | import { ProductService, RequestContext, ID, Product } from "@vendure/core"; 2 | import { ProductRecommendationService } from "./product-recommendations.service"; 3 | import { ProductRecommendation } from "./product-recommendation.entity"; 4 | import { Translated } from "@vendure/core/dist/common/types/locale-types"; 5 | export declare class ProductRecommendationAdminResolver { 6 | private productRecommendationService; 7 | constructor(productRecommendationService: ProductRecommendationService); 8 | updateCrossSellingProducts(ctx: RequestContext, args: { 9 | productId: ID; 10 | productIds: [ID]; 11 | }): Promise; 12 | updateUpSellingProducts(ctx: RequestContext, args: { 13 | productId: ID; 14 | productIds: ID[]; 15 | }): Promise; 16 | productRecommendations(ctx: RequestContext, args: { 17 | productId: ID; 18 | }): Promise; 19 | } 20 | export declare class ProductRecommendationShopResolver { 21 | private productRecommendationService; 22 | constructor(productRecommendationService: ProductRecommendationService); 23 | productRecommendations(ctx: RequestContext, args: { 24 | productId: ID; 25 | }): Promise; 26 | } 27 | export declare class ProductRecommendationEntityResolver { 28 | private productService; 29 | constructor(productService: ProductService); 30 | recommendation(ctx: RequestContext, recommendation: ProductRecommendation): Promise>; 31 | } 32 | export declare class ProductEntityResolver { 33 | private productRecommendationService; 34 | constructor(productRecommendationService: ProductRecommendationService); 35 | recommendations(ctx: RequestContext, product: Product): Promise; 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This vendure plugin adds product recommendations, namely cross- and upselling, to the products. It's up to your frontend to fetch them, a graphql API is provided. 4 | 5 | # Disclaimers 6 | 7 | - I'm not actively supporting this plugin in the sense of that I will ensure its functionality for all possibe use cases or add features, I simply don't have the time for that. This repo just contains a copy of the plugin I'm using for my projects but if you're interested in adding features you are of course welcome to create pull requests. 8 | - There are no tests yet. If you plan to use this plugin in production, you're welcome to create pull requests. 9 | 10 | # Installation 11 | 12 | Step 1): Install `vendure-product-recommendations` by using `npm` or `yarn`: 13 | 14 | `yarn add vendure-product-recommendations` 15 | 16 | Step 2): Import the vendure plugin from `vendure-product-recommendations` and add it the `plugins` section in 17 | 18 | `vendure-config.ts`: 19 | 20 | import { ProductRecommendationsPlugin } from "vendure-product-recommendations"; 21 | ... 22 | export const config: VendureConfig = { 23 | ... 24 | plugins: [ 25 | ..., 26 | ProductRecommendationsPlugin 27 | ] 28 | } 29 | 30 | # Usage 31 | 32 | The following graphql endpoints are added: 33 | 34 | ## Admin 35 | 36 | enum RecommendationType { 37 | CROSSSELL 38 | UPSELL 39 | } 40 | 41 | type ProductRecommendation { 42 | product: Product! 43 | recommendation: Product! 44 | type: RecommendationType! 45 | } 46 | extend type Query { 47 | productRecommendations(productId: ID!): [ProductRecommendation!]! 48 | } 49 | extend type Mutation { 50 | updateCrossSellingProducts(productId: ID!, productIds: [ID!]): Boolean! 51 | updateUpSellingProducts(productId: ID!, productIds: [ID!]): Boolean! 52 | } 53 | 54 | ## Shop 55 | 56 | enum RecommendationType { 57 | CROSSSELL 58 | UPSELL 59 | } 60 | type ProductRecommendation { 61 | product: Product! 62 | recommendation: Product! 63 | type: RecommendationType! 64 | } 65 | extend type Query { 66 | productRecommendations(productId: ID!): [ProductRecommendation!]! 67 | } -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This vendure plugin adds product recommendations, namely cross- and upselling, to the products. It's up to your frontend to fetch them, a graphql API is provided. 4 | 5 | # Disclaimers 6 | 7 | - I'm not actively supporting this plugin in the sense of that I will ensure its functionality for all possibe use cases or add features, I simply don't have the time for that. This repo just contains a copy of the plugin I'm using for my projects but if you're interested in adding features you are of course welcome to create pull requests. 8 | - There are no tests yet. If you plan to use this plugin in production, you're welcome to create pull requests. 9 | 10 | # Installation 11 | 12 | Step 1): Install `vendure-product-recommendations` by using `npm` or `yarn`: 13 | 14 | `yarn add vendure-product-recommendations` 15 | 16 | Step 2): Import the vendure plugin from `vendure-product-recommendations` and add it the `plugins` section in 17 | 18 | `vendure-config.ts`: 19 | 20 | import { ProductRecommendationsPlugin } from "vendure-product-recommendations"; 21 | ... 22 | export const config: VendureConfig = { 23 | ... 24 | plugins: [ 25 | ..., 26 | ProductRecommendationsPlugin 27 | ] 28 | } 29 | 30 | # Usage 31 | 32 | The following graphql endpoints are added: 33 | 34 | ## Admin 35 | 36 | enum RecommendationType { 37 | CROSSSELL 38 | UPSELL 39 | } 40 | 41 | type ProductRecommendation { 42 | product: Product! 43 | recommendation: Product! 44 | type: RecommendationType! 45 | } 46 | extend type Query { 47 | productRecommendations(productId: ID!): [ProductRecommendation!]! 48 | } 49 | extend type Mutation { 50 | updateCrossSellingProducts(productId: ID!, productIds: [ID!]): Boolean! 51 | updateUpSellingProducts(productId: ID!, productIds: [ID!]): Boolean! 52 | } 53 | 54 | ## Shop 55 | 56 | enum RecommendationType { 57 | CROSSSELL 58 | UPSELL 59 | } 60 | type ProductRecommendation { 61 | product: Product! 62 | recommendation: Product! 63 | type: RecommendationType! 64 | } 65 | extend type Query { 66 | productRecommendations(productId: ID!): [ProductRecommendation!]! 67 | } -------------------------------------------------------------------------------- /src/plugin/product-recommendations.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { In } from "typeorm"; 3 | import { FindManyOptions } from "typeorm/find-options/FindManyOptions"; 4 | import { 5 | ID, 6 | assertFound, 7 | Product, 8 | TransactionalConnection, 9 | } from "@vendure/core"; 10 | import { 11 | DeletionResponse, 12 | DeletionResult, 13 | } from "@vendure/common/lib/generated-types"; 14 | import { ProductRecommendation } from "./product-recommendation.entity"; 15 | import { ProductRecommendationInput } from "./index"; 16 | 17 | @Injectable() 18 | export class ProductRecommendationService { 19 | constructor(private connection: TransactionalConnection) {} 20 | 21 | findAll( 22 | options: FindManyOptions | undefined 23 | ): Promise { 24 | return this.connection.getRepository(ProductRecommendation).find(options); 25 | } 26 | findOne(recommendationId: ID): Promise { 27 | return this.connection 28 | .getRepository(ProductRecommendation) 29 | .findOne(recommendationId, { loadEagerRelations: true }); 30 | } 31 | 32 | async create( 33 | input: ProductRecommendationInput 34 | ): Promise { 35 | const recommendation = new ProductRecommendation({ 36 | product: await this.connection 37 | .getRepository(Product) 38 | .findOne(input.product), 39 | recommendation: await this.connection 40 | .getRepository(Product) 41 | .findOne(input.recommendation), 42 | type: input.type, 43 | }); 44 | const newRecommendation = await this.connection 45 | .getRepository(ProductRecommendation) 46 | .save(recommendation); 47 | 48 | return assertFound(this.findOne(newRecommendation.id)); 49 | } 50 | 51 | async delete(ids: ID[]): Promise { 52 | try { 53 | await this.connection.rawConnection 54 | .createQueryBuilder() 55 | .delete() 56 | .from(ProductRecommendation) 57 | .where({ id: In(ids) }) 58 | .execute(); 59 | 60 | return { 61 | result: DeletionResult.DELETED, 62 | }; 63 | } catch (e) { 64 | return { 65 | result: DeletionResult.NOT_DELETED, 66 | message: e instanceof Error ? e.toString() : "", 67 | }; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/plugin/product-recommendation.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | 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; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ProductRecommendation = exports.RecommendationType = void 0; 13 | const core_1 = require("@vendure/core"); 14 | const typeorm_1 = require("typeorm"); 15 | var RecommendationType; 16 | (function (RecommendationType) { 17 | RecommendationType["CROSSSELL"] = "CROSSSELL"; 18 | RecommendationType["UPSELL"] = "UPSELL"; 19 | })(RecommendationType = exports.RecommendationType || (exports.RecommendationType = {})); 20 | let ProductRecommendation = class ProductRecommendation extends core_1.VendureEntity { 21 | constructor(input) { 22 | super(input); 23 | } 24 | }; 25 | __decorate([ 26 | (0, typeorm_1.ManyToOne)((type) => core_1.Product, { 27 | onDelete: "CASCADE", 28 | nullable: false, 29 | eager: true, 30 | }), 31 | (0, typeorm_1.JoinColumn)(), 32 | __metadata("design:type", core_1.Product) 33 | ], ProductRecommendation.prototype, "product", void 0); 34 | __decorate([ 35 | (0, typeorm_1.ManyToOne)((type) => core_1.Product, { 36 | onDelete: "CASCADE", 37 | nullable: false, 38 | eager: true, 39 | }), 40 | (0, typeorm_1.JoinColumn)(), 41 | __metadata("design:type", core_1.Product) 42 | ], ProductRecommendation.prototype, "recommendation", void 0); 43 | __decorate([ 44 | (0, typeorm_1.Column)({ 45 | type: "enum", 46 | enum: RecommendationType, 47 | }), 48 | __metadata("design:type", String) 49 | ], ProductRecommendation.prototype, "type", void 0); 50 | ProductRecommendation = __decorate([ 51 | (0, typeorm_1.Entity)(), 52 | __metadata("design:paramtypes", [Object]) 53 | ], ProductRecommendation); 54 | exports.ProductRecommendation = ProductRecommendation; 55 | -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import { 3 | VendurePlugin, 4 | PluginCommonModule, 5 | ID, 6 | LanguageCode, 7 | } from "@vendure/core"; 8 | 9 | import { 10 | ProductRecommendation, 11 | RecommendationType, 12 | } from "./product-recommendation.entity"; 13 | import { 14 | ProductRecommendationAdminResolver, 15 | ProductRecommendationShopResolver, 16 | ProductRecommendationEntityResolver, 17 | ProductEntityResolver, 18 | } from "./product-recommendations.resolver"; 19 | import { ProductRecommendationService } from "./product-recommendations.service"; 20 | 21 | export type ProductRecommendationInput = { 22 | product: ID; 23 | recommendation: ID; 24 | type: RecommendationType; 25 | }; 26 | 27 | const adminSchemaExtension = gql` 28 | enum RecommendationType { 29 | CROSSSELL 30 | UPSELL 31 | } 32 | 33 | type ProductRecommendation { 34 | product: Product! 35 | recommendation: Product! 36 | type: RecommendationType! 37 | } 38 | extend type Query { 39 | productRecommendations(productId: ID!): [ProductRecommendation!]! 40 | } 41 | 42 | extend type Mutation { 43 | updateCrossSellingProducts(productId: ID!, productIds: [ID!]!): Boolean! 44 | updateUpSellingProducts(productId: ID!, productIds: [ID!]!): Boolean! 45 | } 46 | 47 | extend type Product { 48 | recommendations: [ProductRecommendation!]! 49 | } 50 | `; 51 | 52 | const shopSchemaExtension = gql` 53 | enum RecommendationType { 54 | CROSSSELL 55 | UPSELL 56 | } 57 | 58 | type ProductRecommendation { 59 | product: Product! 60 | recommendation: Product! 61 | type: RecommendationType! 62 | } 63 | extend type Query { 64 | productRecommendations(productId: ID!): [ProductRecommendation!]! 65 | } 66 | extend type Product { 67 | recommendations: [ProductRecommendation!]! 68 | } 69 | `; 70 | 71 | @VendurePlugin({ 72 | imports: [PluginCommonModule], 73 | entities: [ProductRecommendation], 74 | providers: [ProductRecommendationService], 75 | adminApiExtensions: { 76 | schema: adminSchemaExtension, 77 | resolvers: [ 78 | ProductRecommendationAdminResolver, 79 | ProductRecommendationEntityResolver, 80 | ProductEntityResolver, 81 | ], 82 | }, 83 | shopApiExtensions: { 84 | schema: shopSchemaExtension, 85 | resolvers: [ 86 | ProductRecommendationShopResolver, 87 | ProductRecommendationEntityResolver, 88 | ProductEntityResolver, 89 | ], 90 | }, 91 | configuration: (config) => { 92 | config.customFields.Product.push({ 93 | type: "boolean", 94 | name: "productRecommendationsEnabled", 95 | label: [ 96 | { languageCode: LanguageCode.en, value: "Has product recommendations" }, 97 | ], 98 | }); 99 | return config; 100 | }, 101 | }) 102 | export class ProductRecommendationsPlugin {} 103 | -------------------------------------------------------------------------------- /lib/plugin/product-recommendations.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | 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; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ProductRecommendationService = void 0; 13 | const common_1 = require("@nestjs/common"); 14 | const typeorm_1 = require("typeorm"); 15 | const core_1 = require("@vendure/core"); 16 | const generated_types_1 = require("@vendure/common/lib/generated-types"); 17 | const product_recommendation_entity_1 = require("./product-recommendation.entity"); 18 | let ProductRecommendationService = class ProductRecommendationService { 19 | constructor(connection) { 20 | this.connection = connection; 21 | } 22 | findAll(options) { 23 | return this.connection.getRepository(product_recommendation_entity_1.ProductRecommendation).find(options); 24 | } 25 | findOne(recommendationId) { 26 | return this.connection 27 | .getRepository(product_recommendation_entity_1.ProductRecommendation) 28 | .findOne(recommendationId, { loadEagerRelations: true }); 29 | } 30 | async create(input) { 31 | const recommendation = new product_recommendation_entity_1.ProductRecommendation({ 32 | product: await this.connection 33 | .getRepository(core_1.Product) 34 | .findOne(input.product), 35 | recommendation: await this.connection 36 | .getRepository(core_1.Product) 37 | .findOne(input.recommendation), 38 | type: input.type, 39 | }); 40 | const newRecommendation = await this.connection 41 | .getRepository(product_recommendation_entity_1.ProductRecommendation) 42 | .save(recommendation); 43 | return (0, core_1.assertFound)(this.findOne(newRecommendation.id)); 44 | } 45 | async delete(ids) { 46 | try { 47 | await this.connection.rawConnection 48 | .createQueryBuilder() 49 | .delete() 50 | .from(product_recommendation_entity_1.ProductRecommendation) 51 | .where({ id: (0, typeorm_1.In)(ids) }) 52 | .execute(); 53 | return { 54 | result: generated_types_1.DeletionResult.DELETED, 55 | }; 56 | } 57 | catch (e) { 58 | return { 59 | result: generated_types_1.DeletionResult.NOT_DELETED, 60 | message: e instanceof Error ? e.toString() : "", 61 | }; 62 | } 63 | } 64 | }; 65 | ProductRecommendationService = __decorate([ 66 | (0, common_1.Injectable)(), 67 | __metadata("design:paramtypes", [core_1.TransactionalConnection]) 68 | ], ProductRecommendationService); 69 | exports.ProductRecommendationService = ProductRecommendationService; 70 | -------------------------------------------------------------------------------- /lib/plugin/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | 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; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __importDefault = (this && this.__importDefault) || function (mod) { 9 | return (mod && mod.__esModule) ? mod : { "default": mod }; 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ProductRecommendationsPlugin = void 0; 13 | const graphql_tag_1 = __importDefault(require("graphql-tag")); 14 | const core_1 = require("@vendure/core"); 15 | const product_recommendation_entity_1 = require("./product-recommendation.entity"); 16 | const product_recommendations_resolver_1 = require("./product-recommendations.resolver"); 17 | const product_recommendations_service_1 = require("./product-recommendations.service"); 18 | const adminSchemaExtension = (0, graphql_tag_1.default) ` 19 | enum RecommendationType { 20 | CROSSSELL 21 | UPSELL 22 | } 23 | 24 | type ProductRecommendation { 25 | product: Product! 26 | recommendation: Product! 27 | type: RecommendationType! 28 | } 29 | extend type Query { 30 | productRecommendations(productId: ID!): [ProductRecommendation!]! 31 | } 32 | 33 | extend type Mutation { 34 | updateCrossSellingProducts(productId: ID!, productIds: [ID!]!): Boolean! 35 | updateUpSellingProducts(productId: ID!, productIds: [ID!]!): Boolean! 36 | } 37 | 38 | extend type Product { 39 | recommendations: [ProductRecommendation!]! 40 | } 41 | `; 42 | const shopSchemaExtension = (0, graphql_tag_1.default) ` 43 | enum RecommendationType { 44 | CROSSSELL 45 | UPSELL 46 | } 47 | 48 | type ProductRecommendation { 49 | product: Product! 50 | recommendation: Product! 51 | type: RecommendationType! 52 | } 53 | extend type Query { 54 | productRecommendations(productId: ID!): [ProductRecommendation!]! 55 | } 56 | extend type Product { 57 | recommendations: [ProductRecommendation!]! 58 | } 59 | `; 60 | let ProductRecommendationsPlugin = class ProductRecommendationsPlugin { 61 | }; 62 | ProductRecommendationsPlugin = __decorate([ 63 | (0, core_1.VendurePlugin)({ 64 | imports: [core_1.PluginCommonModule], 65 | entities: [product_recommendation_entity_1.ProductRecommendation], 66 | providers: [product_recommendations_service_1.ProductRecommendationService], 67 | adminApiExtensions: { 68 | schema: adminSchemaExtension, 69 | resolvers: [ 70 | product_recommendations_resolver_1.ProductRecommendationAdminResolver, 71 | product_recommendations_resolver_1.ProductRecommendationEntityResolver, 72 | product_recommendations_resolver_1.ProductEntityResolver, 73 | ], 74 | }, 75 | shopApiExtensions: { 76 | schema: shopSchemaExtension, 77 | resolvers: [ 78 | product_recommendations_resolver_1.ProductRecommendationShopResolver, 79 | product_recommendations_resolver_1.ProductRecommendationEntityResolver, 80 | product_recommendations_resolver_1.ProductEntityResolver, 81 | ], 82 | }, 83 | configuration: (config) => { 84 | config.customFields.Product.push({ 85 | type: "boolean", 86 | name: "productRecommendationsEnabled", 87 | label: [ 88 | { languageCode: core_1.LanguageCode.en, value: "Has product recommendations" }, 89 | ], 90 | }); 91 | return config; 92 | }, 93 | }) 94 | ], ProductRecommendationsPlugin); 95 | exports.ProductRecommendationsPlugin = ProductRecommendationsPlugin; 96 | -------------------------------------------------------------------------------- /src/plugin/product-recommendations.resolver.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Args, 3 | Mutation, 4 | Resolver, 5 | Query, 6 | Parent, 7 | ResolveField, 8 | } from "@nestjs/graphql"; 9 | import { 10 | Allow, 11 | Ctx, 12 | ProductService, 13 | RequestContext, 14 | ID, 15 | Product, 16 | Transaction, 17 | } from "@vendure/core"; 18 | import { Permission } from "@vendure/common/lib/generated-types"; 19 | 20 | import { ProductRecommendationService } from "./product-recommendations.service"; 21 | import { 22 | RecommendationType, 23 | ProductRecommendation, 24 | } from "./product-recommendation.entity"; 25 | import { Translated } from "@vendure/core/dist/common/types/locale-types"; 26 | 27 | @Resolver() 28 | export class ProductRecommendationAdminResolver { 29 | constructor( 30 | private productRecommendationService: ProductRecommendationService 31 | ) {} 32 | 33 | @Transaction() 34 | @Mutation() 35 | @Allow(Permission.UpdateCatalog) 36 | async updateCrossSellingProducts( 37 | @Ctx() ctx: RequestContext, 38 | @Args() args: { productId: ID; productIds: [ID] } 39 | ): Promise { 40 | const recommendations: ProductRecommendation[] = await this.productRecommendationService.findAll( 41 | { 42 | where: { product: args.productId, type: RecommendationType.CROSSSELL }, 43 | } 44 | ); 45 | 46 | const recommendationsIds = recommendations.map((r) => r.recommendation.id); 47 | 48 | const toDelete = recommendations 49 | .filter((r) => !args.productIds.includes(r.recommendation.id)) 50 | .map((r) => r.id); 51 | const toCreate = args.productIds.filter( 52 | (r) => !recommendationsIds.includes(r) 53 | ); 54 | 55 | const promises: Promise[] = toCreate.map((id) => 56 | this.productRecommendationService.create({ 57 | product: args.productId, 58 | recommendation: id, 59 | type: RecommendationType.CROSSSELL, 60 | }) 61 | ); 62 | 63 | if (toDelete.length > 0) { 64 | promises.push(this.productRecommendationService.delete(toDelete)); 65 | } 66 | 67 | await Promise.all(promises); 68 | 69 | return true; 70 | } 71 | 72 | @Transaction() 73 | @Mutation() 74 | @Allow(Permission.UpdateCatalog) 75 | async updateUpSellingProducts( 76 | @Ctx() ctx: RequestContext, 77 | @Args() args: { productId: ID; productIds: ID[] } 78 | ): Promise { 79 | const recommendations = await this.productRecommendationService.findAll({ 80 | where: { product: args.productId, type: RecommendationType.UPSELL }, 81 | }); 82 | 83 | const recommendationsIds = recommendations.map((r) => r.recommendation.id); 84 | 85 | const toDelete = recommendations 86 | .filter((r) => !args.productIds.includes(r.recommendation.id)) 87 | .map((r) => r.id); 88 | const toCreate = args.productIds.filter( 89 | (r) => !recommendationsIds.includes(r) 90 | ); 91 | 92 | await Promise.all([ 93 | toCreate.map((id) => 94 | this.productRecommendationService.create({ 95 | product: args.productId, 96 | recommendation: id, 97 | type: RecommendationType.UPSELL, 98 | }) 99 | ), 100 | this.productRecommendationService.delete(toDelete), 101 | ]); 102 | 103 | return true; 104 | } 105 | 106 | @Transaction() 107 | @Query() 108 | async productRecommendations( 109 | @Ctx() ctx: RequestContext, 110 | @Args() args: { productId: ID } 111 | ): Promise { 112 | return await this.productRecommendationService.findAll({ 113 | where: { product: args.productId }, 114 | }); 115 | } 116 | } 117 | 118 | @Resolver() 119 | export class ProductRecommendationShopResolver { 120 | constructor( 121 | private productRecommendationService: ProductRecommendationService 122 | ) {} 123 | 124 | @Query() 125 | async productRecommendations( 126 | @Ctx() ctx: RequestContext, 127 | @Args() args: { productId: ID } 128 | ): Promise { 129 | return await this.productRecommendationService.findAll({ 130 | where: { product: args.productId }, 131 | }); 132 | } 133 | } 134 | 135 | @Resolver("ProductRecommendation") 136 | export class ProductRecommendationEntityResolver { 137 | constructor(private productService: ProductService) {} 138 | 139 | @ResolveField() 140 | async recommendation( 141 | @Ctx() ctx: RequestContext, 142 | @Parent() recommendation: ProductRecommendation 143 | ): Promise> { 144 | const product = await this.productService.findOne( 145 | ctx, 146 | recommendation.recommendation.id 147 | ); 148 | 149 | if (!product) { 150 | throw new Error( 151 | `Invalid database records for product recommendation with the id ${recommendation.id}` 152 | ); 153 | } 154 | 155 | return product; 156 | } 157 | } 158 | 159 | @Resolver("Product") 160 | export class ProductEntityResolver { 161 | constructor( 162 | private productRecommendationService: ProductRecommendationService 163 | ) {} 164 | 165 | @ResolveField() 166 | async recommendations( 167 | @Ctx() ctx: RequestContext, 168 | @Parent() product: Product 169 | ): Promise { 170 | return this.productRecommendationService.findAll({ 171 | where: { product: product.id }, 172 | }); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/plugin/product-recommendations.resolver.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | 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; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | var __param = (this && this.__param) || function (paramIndex, decorator) { 12 | return function (target, key) { decorator(target, key, paramIndex); } 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.ProductEntityResolver = exports.ProductRecommendationEntityResolver = exports.ProductRecommendationShopResolver = exports.ProductRecommendationAdminResolver = void 0; 16 | const graphql_1 = require("@nestjs/graphql"); 17 | const core_1 = require("@vendure/core"); 18 | const generated_types_1 = require("@vendure/common/lib/generated-types"); 19 | const product_recommendations_service_1 = require("./product-recommendations.service"); 20 | const product_recommendation_entity_1 = require("./product-recommendation.entity"); 21 | let ProductRecommendationAdminResolver = class ProductRecommendationAdminResolver { 22 | constructor(productRecommendationService) { 23 | this.productRecommendationService = productRecommendationService; 24 | } 25 | async updateCrossSellingProducts(ctx, args) { 26 | const recommendations = await this.productRecommendationService.findAll({ 27 | where: { product: args.productId, type: product_recommendation_entity_1.RecommendationType.CROSSSELL }, 28 | }); 29 | const recommendationsIds = recommendations.map((r) => r.recommendation.id); 30 | const toDelete = recommendations 31 | .filter((r) => !args.productIds.includes(r.recommendation.id)) 32 | .map((r) => r.id); 33 | const toCreate = args.productIds.filter((r) => !recommendationsIds.includes(r)); 34 | const promises = toCreate.map((id) => this.productRecommendationService.create({ 35 | product: args.productId, 36 | recommendation: id, 37 | type: product_recommendation_entity_1.RecommendationType.CROSSSELL, 38 | })); 39 | if (toDelete.length > 0) { 40 | promises.push(this.productRecommendationService.delete(toDelete)); 41 | } 42 | await Promise.all(promises); 43 | return true; 44 | } 45 | async updateUpSellingProducts(ctx, args) { 46 | const recommendations = await this.productRecommendationService.findAll({ 47 | where: { product: args.productId, type: product_recommendation_entity_1.RecommendationType.UPSELL }, 48 | }); 49 | const recommendationsIds = recommendations.map((r) => r.recommendation.id); 50 | const toDelete = recommendations 51 | .filter((r) => !args.productIds.includes(r.recommendation.id)) 52 | .map((r) => r.id); 53 | const toCreate = args.productIds.filter((r) => !recommendationsIds.includes(r)); 54 | await Promise.all([ 55 | toCreate.map((id) => this.productRecommendationService.create({ 56 | product: args.productId, 57 | recommendation: id, 58 | type: product_recommendation_entity_1.RecommendationType.UPSELL, 59 | })), 60 | this.productRecommendationService.delete(toDelete), 61 | ]); 62 | return true; 63 | } 64 | async productRecommendations(ctx, args) { 65 | return await this.productRecommendationService.findAll({ 66 | where: { product: args.productId }, 67 | }); 68 | } 69 | }; 70 | __decorate([ 71 | (0, core_1.Transaction)(), 72 | (0, graphql_1.Mutation)(), 73 | (0, core_1.Allow)(generated_types_1.Permission.UpdateCatalog), 74 | __param(0, (0, core_1.Ctx)()), 75 | __param(1, (0, graphql_1.Args)()), 76 | __metadata("design:type", Function), 77 | __metadata("design:paramtypes", [core_1.RequestContext, Object]), 78 | __metadata("design:returntype", Promise) 79 | ], ProductRecommendationAdminResolver.prototype, "updateCrossSellingProducts", null); 80 | __decorate([ 81 | (0, core_1.Transaction)(), 82 | (0, graphql_1.Mutation)(), 83 | (0, core_1.Allow)(generated_types_1.Permission.UpdateCatalog), 84 | __param(0, (0, core_1.Ctx)()), 85 | __param(1, (0, graphql_1.Args)()), 86 | __metadata("design:type", Function), 87 | __metadata("design:paramtypes", [core_1.RequestContext, Object]), 88 | __metadata("design:returntype", Promise) 89 | ], ProductRecommendationAdminResolver.prototype, "updateUpSellingProducts", null); 90 | __decorate([ 91 | (0, core_1.Transaction)(), 92 | (0, graphql_1.Query)(), 93 | __param(0, (0, core_1.Ctx)()), 94 | __param(1, (0, graphql_1.Args)()), 95 | __metadata("design:type", Function), 96 | __metadata("design:paramtypes", [core_1.RequestContext, Object]), 97 | __metadata("design:returntype", Promise) 98 | ], ProductRecommendationAdminResolver.prototype, "productRecommendations", null); 99 | ProductRecommendationAdminResolver = __decorate([ 100 | (0, graphql_1.Resolver)(), 101 | __metadata("design:paramtypes", [product_recommendations_service_1.ProductRecommendationService]) 102 | ], ProductRecommendationAdminResolver); 103 | exports.ProductRecommendationAdminResolver = ProductRecommendationAdminResolver; 104 | let ProductRecommendationShopResolver = class ProductRecommendationShopResolver { 105 | constructor(productRecommendationService) { 106 | this.productRecommendationService = productRecommendationService; 107 | } 108 | async productRecommendations(ctx, args) { 109 | return await this.productRecommendationService.findAll({ 110 | where: { product: args.productId }, 111 | }); 112 | } 113 | }; 114 | __decorate([ 115 | (0, graphql_1.Query)(), 116 | __param(0, (0, core_1.Ctx)()), 117 | __param(1, (0, graphql_1.Args)()), 118 | __metadata("design:type", Function), 119 | __metadata("design:paramtypes", [core_1.RequestContext, Object]), 120 | __metadata("design:returntype", Promise) 121 | ], ProductRecommendationShopResolver.prototype, "productRecommendations", null); 122 | ProductRecommendationShopResolver = __decorate([ 123 | (0, graphql_1.Resolver)(), 124 | __metadata("design:paramtypes", [product_recommendations_service_1.ProductRecommendationService]) 125 | ], ProductRecommendationShopResolver); 126 | exports.ProductRecommendationShopResolver = ProductRecommendationShopResolver; 127 | let ProductRecommendationEntityResolver = class ProductRecommendationEntityResolver { 128 | constructor(productService) { 129 | this.productService = productService; 130 | } 131 | async recommendation(ctx, recommendation) { 132 | const product = await this.productService.findOne(ctx, recommendation.recommendation.id); 133 | if (!product) { 134 | throw new Error(`Invalid database records for product recommendation with the id ${recommendation.id}`); 135 | } 136 | return product; 137 | } 138 | }; 139 | __decorate([ 140 | (0, graphql_1.ResolveField)(), 141 | __param(0, (0, core_1.Ctx)()), 142 | __param(1, (0, graphql_1.Parent)()), 143 | __metadata("design:type", Function), 144 | __metadata("design:paramtypes", [core_1.RequestContext, 145 | product_recommendation_entity_1.ProductRecommendation]), 146 | __metadata("design:returntype", Promise) 147 | ], ProductRecommendationEntityResolver.prototype, "recommendation", null); 148 | ProductRecommendationEntityResolver = __decorate([ 149 | (0, graphql_1.Resolver)("ProductRecommendation"), 150 | __metadata("design:paramtypes", [core_1.ProductService]) 151 | ], ProductRecommendationEntityResolver); 152 | exports.ProductRecommendationEntityResolver = ProductRecommendationEntityResolver; 153 | let ProductEntityResolver = class ProductEntityResolver { 154 | constructor(productRecommendationService) { 155 | this.productRecommendationService = productRecommendationService; 156 | } 157 | async recommendations(ctx, product) { 158 | return this.productRecommendationService.findAll({ 159 | where: { product: product.id }, 160 | }); 161 | } 162 | }; 163 | __decorate([ 164 | (0, graphql_1.ResolveField)(), 165 | __param(0, (0, core_1.Ctx)()), 166 | __param(1, (0, graphql_1.Parent)()), 167 | __metadata("design:type", Function), 168 | __metadata("design:paramtypes", [core_1.RequestContext, 169 | core_1.Product]), 170 | __metadata("design:returntype", Promise) 171 | ], ProductEntityResolver.prototype, "recommendations", null); 172 | ProductEntityResolver = __decorate([ 173 | (0, graphql_1.Resolver)("Product"), 174 | __metadata("design:paramtypes", [product_recommendations_service_1.ProductRecommendationService]) 175 | ], ProductEntityResolver); 176 | exports.ProductEntityResolver = ProductEntityResolver; 177 | --------------------------------------------------------------------------------