issue-1636-1664-plugin.ts 5.8 KB

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