feat(medusa): Improve buildQuery as well as refactor the cart service as an example

This commit is contained in:
adrien2p committed 2022-04-21 13:48:16 +02:00
1 parent e7e715ac17
commit b90291b18d
6 files changed
+84 -67

No files matched your search

+62 -61
View File
@@ -1,6 +1,5 @@
import _ from "lodash" import _ from "lodash"
import { MedusaError, Validator } from "medusa-core-utils" import { MedusaError, Validator } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { DeepPartial, EntityManager, In } from "typeorm" import { DeepPartial, EntityManager, In } from "typeorm"
import { IPriceSelectionStrategy } from "../interfaces/price-selection-strategy" import { IPriceSelectionStrategy } from "../interfaces/price-selection-strategy"
import { Address } from "../models/address" import { Address } from "../models/address"
@@ -37,6 +36,8 @@ import InventoryService from "./inventory"
import CustomShippingOptionService from "./custom-shipping-option" import CustomShippingOptionService from "./custom-shipping-option"
import LineItemAdjustmentService from "./line-item-adjustment" import LineItemAdjustmentService from "./line-item-adjustment"
import { LineItemRepository } from "../repositories/line-item" import { LineItemRepository } from "../repositories/line-item"
import { TransactionBaseService } from "../interfaces"
import { buildQuery, setMetadata, validateId } from "../utils"
type InjectedDependencies = { type InjectedDependencies = {
manager: EntityManager manager: EntityManager
@@ -70,14 +71,16 @@ type TotalsConfig = {
/* Provides layer to manipulate carts. /* Provides layer to manipulate carts.
* @implements BaseService * @implements BaseService
*/ */
class CartService extends BaseService { class CartService extends TransactionBaseService<CartService> {
static Events = { static readonly Events = {
CUSTOMER_UPDATED: "cart.customer_updated", CUSTOMER_UPDATED: "cart.customer_updated",
CREATED: "cart.created", CREATED: "cart.created",
UPDATED: "cart.updated", UPDATED: "cart.updated",
} }
protected readonly manager_: EntityManager protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly shippingMethodRepository_: typeof ShippingMethodRepository protected readonly shippingMethodRepository_: typeof ShippingMethodRepository
protected readonly cartRepository_: typeof CartRepository protected readonly cartRepository_: typeof CartRepository
protected readonly addressRepository_: typeof AddressRepository protected readonly addressRepository_: typeof AddressRepository
@@ -124,7 +127,30 @@ class CartService extends BaseService {
lineItemAdjustmentService, lineItemAdjustmentService,
priceSelectionStrategy, priceSelectionStrategy,
}: InjectedDependencies) { }: InjectedDependencies) {
super() super({
manager,
cartRepository,
shippingMethodRepository,
lineItemRepository,
eventBusService,
paymentProviderService,
productService,
productVariantService,
taxProviderService,
regionService,
lineItemService,
shippingOptionService,
customerService,
discountService,
giftCardService,
totalsService,
addressRepository,
paymentSessionRepository,
inventoryService,
customShippingOptionService,
lineItemAdjustmentService,
priceSelectionStrategy,
})
this.manager_ = manager this.manager_ = manager
this.shippingMethodRepository_ = shippingMethodRepository this.shippingMethodRepository_ = shippingMethodRepository
@@ -150,41 +176,6 @@ class CartService extends BaseService {
this.priceSelectionStrategy_ = priceSelectionStrategy this.priceSelectionStrategy_ = priceSelectionStrategy
} }
withTransaction(transactionManager: EntityManager): CartService {
if (!transactionManager) {
return this
}
const cloned = new CartService({
manager: transactionManager,
taxProviderService: this.taxProviderService_,
cartRepository: this.cartRepository_,
lineItemRepository: this.lineItemRepository_,
eventBusService: this.eventBus_,
paymentProviderService: this.paymentProviderService_,
paymentSessionRepository: this.paymentSessionRepository_,
shippingMethodRepository: this.shippingMethodRepository_,
productService: this.productService_,
productVariantService: this.productVariantService_,
regionService: this.regionService_,
lineItemService: this.lineItemService_,
shippingOptionService: this.shippingOptionService_,
customerService: this.customerService_,
discountService: this.discountService_,
totalsService: this.totalsService_,
addressRepository: this.addressRepository_,
giftCardService: this.giftCardService_,
inventoryService: this.inventoryService_,
customShippingOptionService: this.customShippingOptionService_,
lineItemAdjustmentService: this.lineItemAdjustmentService_,
priceSelectionStrategy: this.priceSelectionStrategy_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
protected transformQueryForTotals_( protected transformQueryForTotals_(
config: FindConfig<Cart> config: FindConfig<Cart>
): FindConfig<Cart> & { totalsToSelect: TotalField[] } { ): FindConfig<Cart> & { totalsToSelect: TotalField[] } {
@@ -292,7 +283,7 @@ class CartService extends BaseService {
this.cartRepository_ this.cartRepository_
) )
const query = this.buildQuery_(selector, config) const query = buildQuery<Cart>(selector, config)
return await cartRepo.find(query) return await cartRepo.find(query)
} }
) )
@@ -315,12 +306,12 @@ class CartService extends BaseService {
const cartRepo = transactionManager.getCustomRepository( const cartRepo = transactionManager.getCustomRepository(
this.cartRepository_ this.cartRepository_
) )
const validatedId = this.validateId_(cartId) const validatedId = validateId(cartId)
const { select, relations, totalsToSelect } = const { select, relations, totalsToSelect } =
this.transformQueryForTotals_(options) this.transformQueryForTotals_(options)
const query = this.buildQuery_( const query = buildQuery<Cart>(
{ id: validatedId }, { id: validatedId },
{ ...options, select, relations } { ...options, select, relations }
) )
@@ -714,16 +705,20 @@ class CartService extends BaseService {
* @param shouldAdd - flag to indicate, if we should add or remove * @param shouldAdd - flag to indicate, if we should add or remove
* @return void * @return void
*/ */
async adjustFreeShipping_(cart: Cart, shouldAdd: boolean): Promise<void> { protected async adjustFreeShipping_(
cart: Cart,
shouldAdd: boolean
): Promise<void> {
const transactionManager = this.transactionManager_ ?? this.manager_
if (cart.shipping_methods?.length) { if (cart.shipping_methods?.length) {
const shippingMethodRepository = const shippingMethodRepository = transactionManager.getCustomRepository(
this.transactionManager_.getCustomRepository(
this.shippingMethodRepository_ this.shippingMethodRepository_
) )
// if any free shipping discounts, we ensure to update shipping method amount // if any free shipping discounts, we ensure to update shipping method amount
if (shouldAdd) { if (shouldAdd) {
return shippingMethodRepository.update( await shippingMethodRepository.update(
{ {
id: In( id: In(
cart.shipping_methods.map((shippingMethod) => shippingMethod.id) cart.shipping_methods.map((shippingMethod) => shippingMethod.id)
@@ -855,8 +850,8 @@ class CartService extends BaseService {
) )
} }
if ("metadata" in data) { if (data?.metadata) {
cart.metadata = this.setMetadata_(cart, data.metadata) cart.metadata = setMetadata(cart, data.metadata)
} }
if ("context" in data) { if ("context" in data) {
@@ -1352,7 +1347,7 @@ class CartService extends BaseService {
* @param cartOrCartId - the id of the cart to set payment session for * @param cartOrCartId - the id of the cart to set payment session for
* @return the result of the update operation. * @return the result of the update operation.
*/ */
async setPaymentSessions(cartOrCartId: Cart | string): Promise<Cart> { async setPaymentSessions(cartOrCartId: Cart | string): Promise<void> {
return await this.atomicPhase_( return await this.atomicPhase_(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
const psRepo = transactionManager.getCustomRepository( const psRepo = transactionManager.getCustomRepository(
@@ -1679,6 +1674,8 @@ class CartService extends BaseService {
regionId?: string, regionId?: string,
customer_id?: string customer_id?: string
): Promise<void> { ): Promise<void> {
const transactionManager = this.transactionManager_ ?? this.manager_
// If the cart contains items, we update the price of the items // If the cart contains items, we update the price of the items
// to match the updated region or customer id (keeping the old // to match the updated region or customer id (keeping the old
// value if it exists) // value if it exists)
@@ -1693,7 +1690,7 @@ class CartService extends BaseService {
await Promise.all( await Promise.all(
cart.items.map(async (item) => { cart.items.map(async (item) => {
const availablePrice = await this.priceSelectionStrategy_ const availablePrice = await this.priceSelectionStrategy_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.calculateVariantPrice(item.variant_id, { .calculateVariantPrice(item.variant_id, {
region_id: region.id, region_id: region.id,
currency_code: region.currency_code, currency_code: region.currency_code,
@@ -1708,14 +1705,14 @@ class CartService extends BaseService {
availablePrice.calculatedPrice !== null availablePrice.calculatedPrice !== null
) { ) {
return this.lineItemService_ return this.lineItemService_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.update(item.id, { .update(item.id, {
has_shipping: false, has_shipping: false,
unit_price: availablePrice.calculatedPrice, unit_price: availablePrice.calculatedPrice,
}) })
} else { } else {
await this.lineItemService_ await this.lineItemService_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.delete(item.id) .delete(item.id)
return return
} }
@@ -1737,6 +1734,8 @@ class CartService extends BaseService {
regionId: string, regionId: string,
countryCode: string | null countryCode: string | null
): Promise<void> { ): Promise<void> {
const transactionManager = this.transactionManager_ ?? this.manager_
if (cart.completed_at || cart.payment_authorized_at) { if (cart.completed_at || cart.payment_authorized_at) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
@@ -1750,7 +1749,7 @@ class CartService extends BaseService {
.retrieve(regionId, { .retrieve(regionId, {
relations: ["countries"], relations: ["countries"],
}) })
const addrRepo = this.transactionManager_.getCustomRepository( const addrRepo = transactionManager.getCustomRepository(
this.addressRepository_ this.addressRepository_
) )
cart.region = region cart.region = region
@@ -1828,7 +1827,7 @@ class CartService extends BaseService {
// new shipping method // new shipping method
if (cart.shipping_methods && cart.shipping_methods.length) { if (cart.shipping_methods && cart.shipping_methods.length) {
await this.shippingOptionService_ await this.shippingOptionService_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.deleteShippingMethods(cart.shipping_methods) .deleteShippingMethods(cart.shipping_methods)
} }
@@ -1841,7 +1840,7 @@ class CartService extends BaseService {
cart.gift_cards = [] cart.gift_cards = []
if (cart.payment_sessions && cart.payment_sessions.length) { if (cart.payment_sessions && cart.payment_sessions.length) {
const paymentSessionRepo = this.transactionManager_.getCustomRepository( const paymentSessionRepo = transactionManager.getCustomRepository(
this.paymentSessionRepository_ this.paymentSessionRepository_
) )
await paymentSessionRepo.delete({ await paymentSessionRepo.delete({
@@ -1859,7 +1858,7 @@ class CartService extends BaseService {
* @param cartId - the id of the cart to delete * @param cartId - the id of the cart to delete
* @return the deleted cart or undefined if the cart was not found. * @return the deleted cart or undefined if the cart was not found.
*/ */
async delete(cartId: string): Promise<string> { async delete(cartId: string): Promise<Cart> {
return await this.atomicPhase_( return await this.atomicPhase_(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
const cart = await this.retrieve(cartId, { const cart = await this.retrieve(cartId, {
@@ -1913,7 +1912,7 @@ class CartService extends BaseService {
this.cartRepository_ this.cartRepository_
) )
const validatedId = this.validateId_(cartId) const validatedId = validateId(cartId)
if (typeof key !== "string") { if (typeof key !== "string") {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT, MedusaError.Types.INVALID_ARGUMENT,
@@ -1972,20 +1971,22 @@ class CartService extends BaseService {
} }
protected async refreshAdjustments_(cart: Cart): Promise<void> { protected async refreshAdjustments_(cart: Cart): Promise<void> {
const transactionManager = this.transactionManager_ ?? this.manager_
const nonReturnLineIDs = cart.items const nonReturnLineIDs = cart.items
.filter((item) => !item.is_return) .filter((item) => !item.is_return)
.map((i) => i.id) .map((i) => i.id)
// delete all old non return line item adjustments // delete all old non return line item adjustments
await this.lineItemAdjustmentService_ await this.lineItemAdjustmentService_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.delete({ .delete({
item_id: nonReturnLineIDs, item_id: nonReturnLineIDs,
}) })
// potentially create/update line item adjustments // potentially create/update line item adjustments
await this.lineItemAdjustmentService_ await this.lineItemAdjustmentService_
.withTransaction(this.transactionManager_) .withTransaction(transactionManager)
.createAdjustments(cart) .createAdjustments(cart)
} }
@@ -2001,7 +2002,7 @@ class CartService extends BaseService {
const cartRepo = transactionManager.getCustomRepository( const cartRepo = transactionManager.getCustomRepository(
this.cartRepository_ this.cartRepository_
) )
const validatedId = this.validateId_(cartId) const validatedId = validateId(cartId)
if (typeof key !== "string") { if (typeof key !== "string") {
throw new MedusaError( throw new MedusaError(
+1 -1
View File
@@ -71,5 +71,5 @@ export type CartUpdateProps = {
discounts?: Discount[] discounts?: Discount[]
customer_id?: string customer_id?: string
context?: object context?: object
metadata?: object metadata?: Record<string, unknown>
} }
+16 -3
View File
@@ -1,7 +1,20 @@
import { FindConfig, Writable } from "../types/common" import {
DateComparisonOperator,
FindConfig,
NumericalComparisonOperator,
StringComparisonOperator,
Writable,
} from "../types/common"
import { FindOperator, In, Raw } from "typeorm" import { FindOperator, In, Raw } from "typeorm"
type Selector<TEntity> = { [key in keyof TEntity]?: unknown } type Selector<TEntity> = {
[key in keyof TEntity]?: TEntity[key]
| TEntity[key][]
| DateComparisonOperator
| StringComparisonOperator
| NumericalComparisonOperator
}
/** /**
* Used to build TypeORM queries. * Used to build TypeORM queries.
* @param selector The selector * @param selector The selector
@@ -16,7 +29,7 @@ export function buildQuery<TEntity = unknown>(
withDeleted?: boolean withDeleted?: boolean
} { } {
const build = ( const build = (
obj: Record<string, unknown> obj: Selector<TEntity>
): Partial<Writable<TEntity>> => { ): Partial<Writable<TEntity>> => {
return Object.entries(obj).reduce((acc, [key, value]: any) => { return Object.entries(obj).reduce((acc, [key, value]: any) => {
// Undefined values indicate that they have no significance to the query. // Undefined values indicate that they have no significance to the query.
+3
View File
@@ -0,0 +1,3 @@
export * from './build-query'
export * from './set-metadata'
export * from './validate-id'
+1 -1
View File
@@ -6,7 +6,7 @@ import { MedusaError } from "medusa-core-utils/dist"
* @param metadata - the metadata to set * @param metadata - the metadata to set
* @return resolves to the updated result. * @return resolves to the updated result.
*/ */
export function setMetadata_( export function setMetadata(
obj: { metadata: Record<string, unknown> }, obj: { metadata: Record<string, unknown> },
metadata: Record<string, unknown> metadata: Record<string, unknown>
): Record<string, unknown> { ): Record<string, unknown> {
+1 -1
View File
@@ -8,7 +8,7 @@
*/ */
import { MedusaError } from "medusa-core-utils/dist" import { MedusaError } from "medusa-core-utils/dist"
export function validateId_( export function validateId(
rawId: string, rawId: string,
config: { prefix?: string; length?: number } = {} config: { prefix?: string; length?: number } = {}
): string { ): string {