feat(medusa): Cart custom query strategy (#4083)
This commit is contained in:
committed by
GitHub
parent
6511959e23
commit
a666462333
5
.changeset/eighty-eels-pretend.md
Normal file
5
.changeset/eighty-eels-pretend.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
feat(medusa): Cart custom query strategy
|
||||
@@ -1,11 +1,11 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { Cart } from "../../../../../../models"
|
||||
import { CartService, LineItemService } from "../../../../../../services"
|
||||
import { WithRequiredProperty } from "../../../../../../types/common"
|
||||
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
||||
import { FlagRouter } from "../../../../../../utils/flag-router"
|
||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
|
||||
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
||||
import { WithRequiredProperty } from "../../../../../../types/common"
|
||||
import { Cart } from "../../../../../../models"
|
||||
|
||||
export const CreateLineItemSteps = {
|
||||
STARTED: "started",
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterLoad,
|
||||
BeforeInsert,
|
||||
Column,
|
||||
Entity,
|
||||
@@ -390,6 +391,13 @@ export class Cart extends SoftDeletableEntity {
|
||||
gift_card_total?: number
|
||||
gift_card_tax_total?: number
|
||||
|
||||
@AfterLoad()
|
||||
private afterLoad(): void {
|
||||
if (this.payment_sessions) {
|
||||
this.payment_session = this.payment_sessions.find((p) => p.is_selected)!
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "cart")
|
||||
|
||||
@@ -1,16 +1,56 @@
|
||||
import { ExtendedFindConfig } from "@medusajs/types"
|
||||
import { objectToStringPath } from "@medusajs/utils"
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { FindManyOptions, FindOptionsRelations, In } from "typeorm"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { Cart } from "../models"
|
||||
|
||||
export const CartRepository = dataSource.getRepository(Cart).extend({
|
||||
async findOne(options: ExtendedFindConfig<Cart>) {
|
||||
const [cart] = await this.find(options)
|
||||
async findWithRelations(
|
||||
relations: FindOptionsRelations<Cart> = {},
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||
): Promise<Cart[]> {
|
||||
const entities = await this.find(optionsWithoutRelations)
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (cart?.payment_sessions?.length) {
|
||||
cart.payment_session = cart.payment_sessions.find((p) => p.is_selected)!
|
||||
const groupedRelations = {}
|
||||
for (const rel of objectToStringPath(relations)) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
return cart
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||
return this.find({
|
||||
where: { id: In(entitiesIds) },
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
merge({}, ...entityAndRelations)
|
||||
)
|
||||
},
|
||||
|
||||
async findOneWithRelations(
|
||||
relations: FindOptionsRelations<Cart> = {},
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||
): Promise<Cart> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const result = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
},
|
||||
})
|
||||
export default CartRepository
|
||||
|
||||
@@ -65,7 +65,8 @@ describe("CartService", () => {
|
||||
describe("retrieve", () => {
|
||||
let result
|
||||
const cartRepository = MockRepository({
|
||||
findOne: () => Promise.resolve({ id: IdMap.getId("emptyCart") }),
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({ id: IdMap.getId("emptyCart") }),
|
||||
})
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
@@ -81,13 +82,15 @@ describe("CartService", () => {
|
||||
})
|
||||
|
||||
it("calls cart model functions", () => {
|
||||
expect(cartRepository.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(cartRepository.findOne).toHaveBeenCalledWith({
|
||||
relationLoadStrategy: "query",
|
||||
where: { id: IdMap.getId("emptyCart") },
|
||||
select: undefined,
|
||||
relations: undefined,
|
||||
})
|
||||
expect(cartRepository.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||
expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
|
||||
{},
|
||||
{
|
||||
where: { id: IdMap.getId("emptyCart") },
|
||||
select: undefined,
|
||||
relations: undefined,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -126,7 +129,9 @@ describe("CartService", () => {
|
||||
)
|
||||
|
||||
expect(cartRepository.findOne).toBeCalledTimes(1)
|
||||
expect(cartRepository.findOne).toBeCalledWith({ where: { id } })
|
||||
expect(cartRepository.findOne).toBeCalledWith({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
expect(cartRepository.save).toBeCalledTimes(1)
|
||||
expect(cartRepository.save).toBeCalledWith({
|
||||
@@ -164,7 +169,7 @@ describe("CartService", () => {
|
||||
|
||||
const addressRepository = MockRepository({
|
||||
create: (c) => c,
|
||||
findOne: (id) => {
|
||||
findOneWithRelations: (id) => {
|
||||
return {
|
||||
id,
|
||||
first_name: "LeBron",
|
||||
@@ -343,7 +348,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cartWithLine"),
|
||||
@@ -584,7 +589,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cartWithLine"),
|
||||
@@ -670,7 +675,7 @@ describe("CartService", () => {
|
||||
},
|
||||
}
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("withShipping")) {
|
||||
return Promise.resolve({
|
||||
shipping_methods: [
|
||||
@@ -813,7 +818,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("update", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === "withpays") {
|
||||
return Promise.resolve({
|
||||
payment_sessions: [
|
||||
@@ -844,25 +849,25 @@ describe("CartService", () => {
|
||||
cartService.setPaymentSessions = jest.fn()
|
||||
await cartService.update("withpays", {})
|
||||
|
||||
expect(cartRepository.findOne).toHaveBeenCalledWith(
|
||||
expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
relations: {
|
||||
billing_address: true,
|
||||
customer: true,
|
||||
discounts: {
|
||||
rule: true,
|
||||
},
|
||||
gift_cards: true,
|
||||
items: {
|
||||
variant: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
payment_sessions: true,
|
||||
region: { countries: true },
|
||||
shipping_address: true,
|
||||
shipping_methods: true,
|
||||
billing_address: true,
|
||||
customer: true,
|
||||
discounts: {
|
||||
rule: true,
|
||||
},
|
||||
gift_cards: true,
|
||||
items: {
|
||||
variant: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
payment_sessions: true,
|
||||
region: { countries: true },
|
||||
shipping_address: true,
|
||||
shipping_methods: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
select: undefined,
|
||||
where: {
|
||||
id: "withpays",
|
||||
@@ -907,7 +912,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cannot")) {
|
||||
return Promise.resolve({
|
||||
items: [
|
||||
@@ -1019,7 +1024,7 @@ describe("CartService", () => {
|
||||
},
|
||||
}
|
||||
const cartRepository = MockRepository({
|
||||
findOne: () => Promise.resolve({}),
|
||||
findOneWithRelations: () => Promise.resolve({}),
|
||||
})
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
@@ -1091,7 +1096,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("updateBillingAddress", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: () =>
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
region: { countries: [{ iso_2: "us" }] },
|
||||
}),
|
||||
@@ -1153,7 +1158,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("updateShippingAddress", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: () =>
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
region: { countries: [{ iso_2: "us" }] },
|
||||
}),
|
||||
@@ -1288,7 +1293,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: () =>
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
items: [
|
||||
{
|
||||
@@ -1415,7 +1420,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("setPaymentSession", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cartWithLine")) {
|
||||
return Promise.resolve({
|
||||
total: 100,
|
||||
@@ -1623,7 +1628,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cart-to-filter")) {
|
||||
return Promise.resolve(cart3)
|
||||
}
|
||||
@@ -1829,7 +1834,7 @@ describe("CartService", () => {
|
||||
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("lines"):
|
||||
return Promise.resolve(cart3)
|
||||
@@ -2028,7 +2033,7 @@ describe("CartService", () => {
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("with-d")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cart"),
|
||||
@@ -2517,7 +2522,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("removeDiscount", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: (q) => {
|
||||
findOneWithRelations: (rel, q) => {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("cart"),
|
||||
discounts: [
|
||||
|
||||
@@ -231,13 +231,15 @@ class CartService extends TransactionBaseService {
|
||||
const cartRepo = this.activeManager_.withRepository(this.cartRepository_)
|
||||
|
||||
const query = buildQuery({ id: cartId }, options)
|
||||
query.relationLoadStrategy = "query"
|
||||
|
||||
if ((options.select || []).length === 0) {
|
||||
query.select = undefined
|
||||
}
|
||||
|
||||
const raw = await cartRepo.findOne(query)
|
||||
const queryRelations = { ...query.relations }
|
||||
delete query.relations
|
||||
|
||||
const raw = await cartRepo.findOneWithRelations(queryRelations, query)
|
||||
|
||||
if (!raw) {
|
||||
throw new MedusaError(
|
||||
|
||||
Reference in New Issue
Block a user