Преглед изворни кода

docs(core): Improve docs for ActiveOrderStrategy

Michael Bromley пре 3 година
родитељ
комит
71fd2e26ad

+ 6 - 0
docs/content/storefront/order-workflow/_index.md

@@ -225,3 +225,9 @@ query OrderByCode($code: String!) {
   }
 }
 ```
+
+## ActiveOrderStrategy
+
+In the above examples, the active Order is always associated with the current session and is therefore implicit - which is why there is no need to pass an ID to each of the above operations.
+
+Sometimes you _do_ want to be able to explicitly specify the Order you wish to operate on. In this case you need to define a custom [ActiveOrderStrategy]({{< relref "active-order-strategy" >}}).

+ 2 - 3
packages/core/e2e/fixtures/test-plugins/token-active-order-plugin.ts

@@ -12,10 +12,9 @@ import {
     RequestContext,
     Transaction,
     TransactionalConnection,
+    UserInputError,
     VendurePlugin,
 } from '@vendure/core';
-import { CustomOrderFields } from '@vendure/core/dist/entity/custom-entity-fields';
-import { UserInputError } from 'apollo-server-express';
 import gql from 'graphql-tag';
 
 declare module '@vendure/core/dist/entity/custom-entity-fields' {
@@ -24,7 +23,7 @@ declare module '@vendure/core/dist/entity/custom-entity-fields' {
     }
 }
 
-class TokenActiveOrderStrategy implements ActiveOrderStrategy {
+class TokenActiveOrderStrategy implements ActiveOrderStrategy<{ token: string }> {
     readonly name = 'orderToken';
 
     private connection: TransactionalConnection;

+ 117 - 10
packages/core/src/config/order/active-order-strategy.ts

@@ -14,9 +14,118 @@ export const ACTIVE_ORDER_INPUT_FIELD_NAME = 'activeOrderInput';
  * and set it on the current Session, and then read the session to obtain the active Order.
  * This behaviour is defined by the {@link DefaultActiveOrderStrategy}.
  *
+ * The `InputType` generic argument should correspond to the input type defined by the
+ * `defineInputType()` method.
+ *
+ * When `defineInputType()` is used, then the following Shop API operations will receive an additional
+ * `activeOrderInput` argument allowing the active order input to be specified:
+ *
+ * - `activeOrder`
+ * - `eligibleShippingMethods`
+ * - `eligiblePaymentMethods`
+ * - `nextOrderStates`
+ * - `addItemToOrder`
+ * - `adjustOrderLine`
+ * - `removeOrderLine`
+ * - `removeAllOrderLines`
+ * - `applyCouponCode`
+ * - `removeCouponCode`
+ * - `addPaymentToOrder`
+ * - `setCustomerForOrder`
+ * - `setOrderShippingAddress`
+ * - `setOrderBillingAddress`
+ * - `setOrderShippingMethod`
+ * - `setOrderCustomFields`
+ * - `transitionOrderToState`
+ *
+ * @example
+ * ```GraphQL {hl_lines=[5]}
+ * mutation AddItemToOrder {
+ *   addItemToOrder(
+ *     productVariantId: 42,
+ *     quantity: 1,
+ *     activeOrderInput: { token: "123456" }
+ *   ) {
+ *     ...on Order {
+ *       id
+ *       # ...etc
+ *     }
+ *   }
+ * }
+ * ```
+ *
+ * @example
+ * ```TypeScript
+ * import { ID } from '\@vendure/common/lib/shared-types';
+ * import {
+ *   ActiveOrderStrategy,
+ *   CustomerService,
+ *   idsAreEqual,
+ *   Injector,
+ *   Order,
+ *   OrderService,
+ *   RequestContext,
+ *   TransactionalConnection,
+ * } from '\@vendure/core';
+ * import gql from 'graphql-tag';
+ *
+ * // This strategy assumes a "orderToken" custom field is defined on the Order
+ * // entity, and uses that token to perform a lookup to determine the active Order.
+ * //
+ * // Additionally, it does _not_ define a `createActiveOrder()` method, which
+ * // means that a custom mutation would be required to create the initial Order in
+ * // the first place and set the "orderToken" custom field.
+ * class TokenActiveOrderStrategy implements ActiveOrderStrategy<{ token: string }> {
+ *   readonly name = 'orderToken';
+ *
+ *   private connection: TransactionalConnection;
+ *   private orderService: OrderService;
+ *
+ *   init(injector: Injector) {
+ *     this.connection = injector.get(TransactionalConnection);
+ *     this.orderService = injector.get(OrderService);
+ *   }
+ *
+ *   defineInputType = () => gql`
+ *     input OrderTokenActiveOrderInput {
+ *       token: String
+ *     }
+ *   `;
+ *
+ *   async determineActiveOrder(ctx: RequestContext, input: { token: string }) {
+ *     const qb = this.connection
+ *       .getRepository(ctx, Order)
+ *       .createQueryBuilder('order')
+ *       .leftJoinAndSelect('order.customer', 'customer')
+ *       .leftJoinAndSelect('customer.user', 'user')
+ *       .where('order.customFields.orderToken = :orderToken', { orderToken: input.token });
+ *
+ *     const order = await qb.getOne();
+ *     if (!order) {
+ *       return;
+ *     }
+ *     // Ensure the active user is the owner of this Order
+ *     const orderUserId = order.customer && order.customer.user && order.customer.user.id;
+ *     if (order.customer && idsAreEqual(orderUserId, ctx.activeUserId)) {
+ *       return order;
+ *     }
+ *   }
+ * }
+ *
+ * // in vendure-config.ts
+ * export const config = {
+ *   // ...
+ *   orderOptions: {
+ *     activeOrderStrategy: new TokenActiveOrderStrategy(),
+ *   },
+ * }
+ * ```
+ *
  * @since 1.9.0
+ * @docsCategory orders
  */
-export interface ActiveOrderStrategy extends InjectableStrategy {
+export interface ActiveOrderStrategy<InputType extends Record<string, any> | void = void>
+    extends InjectableStrategy {
     /**
      * @description
      * The name of the strategy, e.g. "orderByToken", which will also be used as the
@@ -26,10 +135,8 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
 
     /**
      * @description
-     * Defines the type of the GraphQL Input object expected by the `authenticate`
-     * mutation. The final input object will be a map, with the key being the name
-     * of the strategy. The shape of the input object should match the generic `Data`
-     * type argument.
+     * Defines the type of the GraphQL Input object expected by the `activeOrderInput`
+     * input argument.
      *
      * @example
      * For example, given the following:
@@ -44,8 +151,8 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
      * }
      * ```
      *
-     * assuming the strategy name is "my_auth", then the resulting call to `authenticate`
-     * would look like:
+     * assuming the strategy name is "orderByToken", then the resulting call to `activeOrder` (or any of the other
+     * affected Shop API operations) would look like:
      *
      * ```GraphQL
      * activeOrder(activeOrderInput: {
@@ -70,15 +177,15 @@ export interface ActiveOrderStrategy extends InjectableStrategy {
      * If automatic creation of an Order does not make sense in your strategy, then leave this method
      * undefined. You'll then need to take care of creating an order manually by defining a custom mutation.
      */
-    createActiveOrder?: (ctx: RequestContext, inputs: any) => Promise<Order>;
+    createActiveOrder?: (ctx: RequestContext, input: InputType) => Promise<Order>;
 
     /**
      * @description
      * This method is used to determine the active Order based on the current RequestContext in addition to any
      * input values provided, as defined by the `defineInputType` method of this strategy.
      *
-     * Note that this method is invoked frequently so you should aim to keep it efficient. The returned Order,
+     * Note that this method is invoked frequently, so you should aim to keep it efficient. The returned Order,
      * for example, does not need to have its various relations joined.
      */
-    determineActiveOrder(ctx: RequestContext, inputs: any): Promise<Order | undefined>;
+    determineActiveOrder(ctx: RequestContext, input: InputType): Promise<Order | undefined>;
 }

+ 1 - 2
packages/core/src/config/order/default-active-order-strategy.ts

@@ -3,8 +3,6 @@ import { InternalServerError } from '../../common/error/errors';
 import { Injector } from '../../common/injector';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { Order } from '../../entity/order/order.entity';
-// import { OrderService } from '../../service/services/order.service';
-// import { SessionService } from '../../service/services/session.service';
 
 import { ActiveOrderStrategy } from './active-order-strategy';
 
@@ -15,6 +13,7 @@ import { ActiveOrderStrategy } from './active-order-strategy';
  * session which is part of the RequestContext.
  *
  * @since 1.9.0
+ * @docsCategory orders
  */
 export class DefaultActiveOrderStrategy implements ActiveOrderStrategy {
     private connection: TransactionalConnection;

+ 1 - 1
packages/core/src/config/vendure-config.ts

@@ -569,7 +569,7 @@ export interface OrderOptions {
      * @since 1.9.0
      * @default DefaultActiveOrderStrategy
      */
-    activeOrderStrategy?: ActiveOrderStrategy | ActiveOrderStrategy[];
+    activeOrderStrategy?: ActiveOrderStrategy<any> | Array<ActiveOrderStrategy<any>>;
 }
 
 /**

+ 2 - 2
packages/core/src/service/helpers/active-order/active-order.service.ts

@@ -87,7 +87,7 @@ export class ActiveOrderService {
     ): Promise<Order>;
     async getActiveOrder(
         ctx: RequestContext,
-        input: { [strategyName: string]: any } | undefined,
+        input: { [strategyName: string]: Record<string, any> | undefined } | undefined,
         createIfNotExists = false,
     ): Promise<Order | undefined> {
         let order: any;
@@ -103,7 +103,7 @@ export class ActiveOrderService {
                     break;
                 }
                 if (createIfNotExists && typeof strategy.createActiveOrder === 'function') {
-                    order = await strategy.createActiveOrder(ctx, input);
+                    order = await strategy.createActiveOrder(ctx, strategyInput);
                 }
                 if (order) {
                     break;