issue-1636-1664-plugin.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { OnApplicationBootstrap } from '@nestjs/common';
  2. import { Args, Query, Resolver } from '@nestjs/graphql';
  3. import { ID } from '@vendure/common/lib/shared-types';
  4. import {
  5. Asset,
  6. Channel,
  7. Ctx,
  8. Customer,
  9. PluginCommonModule,
  10. Product,
  11. RequestContext,
  12. TransactionalConnection,
  13. User,
  14. VendureEntity,
  15. VendurePlugin,
  16. } from '@vendure/core';
  17. import gql from 'graphql-tag';
  18. import { Entity, JoinColumn, OneToOne } from 'typeorm';
  19. import { vi } from 'vitest';
  20. import { ProfileAsset } from './profile-asset.entity';
  21. import { Profile } from './profile.entity';
  22. @Entity()
  23. export class Vendor extends VendureEntity {
  24. constructor() {
  25. super();
  26. }
  27. @OneToOne(type => Product, { eager: true })
  28. @JoinColumn()
  29. featuredProduct: Product;
  30. }
  31. @Resolver()
  32. class TestResolver1636 {
  33. constructor(private connection: TransactionalConnection) {}
  34. @Query()
  35. async getAssetTest(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
  36. const asset = await this.connection.findOneInChannel(ctx, Asset, args.id, ctx.channelId, {
  37. relations: ['customFields.single', 'customFields.multi'],
  38. });
  39. TestPlugin1636_1664.testResolverSpy(asset);
  40. return true;
  41. }
  42. }
  43. const profileType = gql`
  44. type Profile implements Node {
  45. id: ID!
  46. createdAt: DateTime!
  47. updatedAt: DateTime!
  48. name: String!
  49. user: User!
  50. }
  51. `;
  52. /**
  53. * Testing https://github.com/vendure-ecommerce/vendure/issues/1636
  54. *
  55. * and
  56. *
  57. * https://github.com/vendure-ecommerce/vendure/issues/1664
  58. */
  59. @VendurePlugin({
  60. imports: [PluginCommonModule],
  61. entities: [Vendor, Profile, ProfileAsset],
  62. shopApiExtensions: {
  63. schema: gql`
  64. extend type Query {
  65. getAssetTest(id: ID!): Boolean!
  66. }
  67. type Vendor {
  68. id: ID
  69. featuredProduct: Product
  70. }
  71. `,
  72. resolvers: [TestResolver1636],
  73. },
  74. adminApiExtensions: {
  75. schema: gql`
  76. type Vendor {
  77. id: ID
  78. featuredProduct: Product
  79. }
  80. type Profile implements Node {
  81. id: ID!
  82. createdAt: DateTime!
  83. updatedAt: DateTime!
  84. name: String!
  85. user: User!
  86. }
  87. `,
  88. resolvers: [],
  89. },
  90. configuration: config => {
  91. config.customFields.Product.push(
  92. {
  93. name: 'cfVendor',
  94. type: 'relation',
  95. entity: Vendor,
  96. graphQLType: 'Vendor',
  97. list: false,
  98. internal: false,
  99. public: true,
  100. },
  101. {
  102. name: 'owner',
  103. nullable: true,
  104. type: 'relation',
  105. // Using the Channel entity rather than User as in the example comment at
  106. // https://github.com/vendure-ecommerce/vendure/issues/1664#issuecomment-1293916504
  107. // because using a User causes a recursive infinite loop in TypeORM between
  108. // Product > User > Vendor > Product etc.
  109. entity: Channel,
  110. public: false,
  111. eager: true, // needs to be eager to enable indexing of user->profile attributes like name, etc.
  112. readonly: true,
  113. },
  114. );
  115. config.customFields.User.push({
  116. name: 'cfVendor',
  117. type: 'relation',
  118. entity: Vendor,
  119. graphQLType: 'Vendor',
  120. list: false,
  121. eager: true,
  122. internal: false,
  123. public: true,
  124. });
  125. config.customFields.Channel.push({
  126. name: 'profile',
  127. type: 'relation',
  128. entity: Profile,
  129. nullable: true,
  130. public: false,
  131. internal: false,
  132. readonly: true,
  133. eager: true, // needs to be eager to enable indexing of profile attributes like name, etc.
  134. });
  135. config.customFields.Order.push({
  136. name: 'productOwner',
  137. nullable: true,
  138. type: 'relation',
  139. entity: User,
  140. public: false,
  141. eager: true,
  142. readonly: true,
  143. });
  144. return config;
  145. },
  146. })
  147. // eslint-disable-next-line @typescript-eslint/naming-convention
  148. export class TestPlugin1636_1664 implements OnApplicationBootstrap {
  149. static testResolverSpy = vi.fn();
  150. constructor(private connection: TransactionalConnection) {}
  151. async onApplicationBootstrap() {
  152. const profilesCount = await this.connection.rawConnection.getRepository(Profile).count();
  153. if (0 < profilesCount) {
  154. return;
  155. }
  156. // Create a Profile and assign it to all the products
  157. const channels = await this.connection.rawConnection.getRepository(Channel).find();
  158. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  159. const channel = channels[0]!;
  160. const profile = await this.connection.rawConnection.getRepository(Profile).save(
  161. new Profile({
  162. name: 'Test Profile',
  163. }),
  164. );
  165. (channel.customFields as any).profile = profile;
  166. await this.connection.rawConnection.getRepository(Channel).save(channel);
  167. const asset = await this.connection.rawConnection.getRepository(Asset).findOne({ where: { id: 1 } });
  168. if (asset) {
  169. const profileAsset = this.connection.rawConnection.getRepository(ProfileAsset).save({
  170. asset,
  171. profile,
  172. });
  173. }
  174. const products = await this.connection.rawConnection.getRepository(Product).find();
  175. for (const product of products) {
  176. (product.customFields as any).owner = channel;
  177. await this.connection.rawConnection.getRepository(Product).save(product);
  178. }
  179. }
  180. }