feat(medusa): Cart custom query strategy (#4083)

This commit is contained in:
Oliver Windall Juhl
2023-05-12 14:06:55 +02:00
committed by GitHub
parent 6511959e23
commit a666462333
6 changed files with 112 additions and 52 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): Cart custom query strategy
@@ -1,11 +1,11 @@
import { AwilixContainer } from "awilix" import { AwilixContainer } from "awilix"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { Cart } from "../../../../../../models"
import { CartService, LineItemService } from "../../../../../../services" import { CartService, LineItemService } from "../../../../../../services"
import { WithRequiredProperty } from "../../../../../../types/common"
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
import { FlagRouter } from "../../../../../../utils/flag-router" import { FlagRouter } from "../../../../../../utils/flag-router"
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index" import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
import { WithRequiredProperty } from "../../../../../../types/common"
import { Cart } from "../../../../../../models"
export const CreateLineItemSteps = { export const CreateLineItemSteps = {
STARTED: "started", STARTED: "started",
+8
View File
@@ -216,6 +216,7 @@
*/ */
import { import {
AfterLoad,
BeforeInsert, BeforeInsert,
Column, Column,
Entity, Entity,
@@ -390,6 +391,13 @@ export class Cart extends SoftDeletableEntity {
gift_card_total?: number gift_card_total?: number
gift_card_tax_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() @BeforeInsert()
private beforeInsert(): void { private beforeInsert(): void {
this.id = generateEntityId(this.id, "cart") this.id = generateEntityId(this.id, "cart")
+46 -6
View File
@@ -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 { dataSource } from "../loaders/database"
import { Cart } from "../models" import { Cart } from "../models"
export const CartRepository = dataSource.getRepository(Cart).extend({ export const CartRepository = dataSource.getRepository(Cart).extend({
async findOne(options: ExtendedFindConfig<Cart>) { async findWithRelations(
const [cart] = await this.find(options) 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) { const groupedRelations = {}
cart.payment_session = cart.payment_sessions.find((p) => p.is_selected)! 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 export default CartRepository
+46 -41
View File
@@ -65,7 +65,8 @@ describe("CartService", () => {
describe("retrieve", () => { describe("retrieve", () => {
let result let result
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: () => Promise.resolve({ id: IdMap.getId("emptyCart") }), findOneWithRelations: () =>
Promise.resolve({ id: IdMap.getId("emptyCart") }),
}) })
beforeAll(async () => { beforeAll(async () => {
jest.clearAllMocks() jest.clearAllMocks()
@@ -81,13 +82,15 @@ describe("CartService", () => {
}) })
it("calls cart model functions", () => { it("calls cart model functions", () => {
expect(cartRepository.findOne).toHaveBeenCalledTimes(1) expect(cartRepository.findOneWithRelations).toHaveBeenCalledTimes(1)
expect(cartRepository.findOne).toHaveBeenCalledWith({ expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
relationLoadStrategy: "query", {},
where: { id: IdMap.getId("emptyCart") }, {
select: undefined, where: { id: IdMap.getId("emptyCart") },
relations: undefined, select: undefined,
}) relations: undefined,
}
)
}) })
}) })
@@ -126,7 +129,9 @@ describe("CartService", () => {
) )
expect(cartRepository.findOne).toBeCalledTimes(1) 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).toBeCalledTimes(1)
expect(cartRepository.save).toBeCalledWith({ expect(cartRepository.save).toBeCalledWith({
@@ -164,7 +169,7 @@ describe("CartService", () => {
const addressRepository = MockRepository({ const addressRepository = MockRepository({
create: (c) => c, create: (c) => c,
findOne: (id) => { findOneWithRelations: (id) => {
return { return {
id, id,
first_name: "LeBron", first_name: "LeBron",
@@ -343,7 +348,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cartWithLine")) { if (q.where.id === IdMap.getId("cartWithLine")) {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("cartWithLine"), id: IdMap.getId("cartWithLine"),
@@ -584,7 +589,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cartWithLine")) { if (q.where.id === IdMap.getId("cartWithLine")) {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("cartWithLine"), id: IdMap.getId("cartWithLine"),
@@ -670,7 +675,7 @@ describe("CartService", () => {
}, },
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("withShipping")) { if (q.where.id === IdMap.getId("withShipping")) {
return Promise.resolve({ return Promise.resolve({
shipping_methods: [ shipping_methods: [
@@ -813,7 +818,7 @@ describe("CartService", () => {
describe("update", () => { describe("update", () => {
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === "withpays") { if (q.where.id === "withpays") {
return Promise.resolve({ return Promise.resolve({
payment_sessions: [ payment_sessions: [
@@ -844,25 +849,25 @@ describe("CartService", () => {
cartService.setPaymentSessions = jest.fn() cartService.setPaymentSessions = jest.fn()
await cartService.update("withpays", {}) await cartService.update("withpays", {})
expect(cartRepository.findOne).toHaveBeenCalledWith( expect(cartRepository.findOneWithRelations).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
relations: { billing_address: true,
billing_address: true, customer: true,
customer: true, discounts: {
discounts: { rule: true,
rule: true,
},
gift_cards: true,
items: {
variant: {
product: true,
},
},
payment_sessions: true,
region: { countries: true },
shipping_address: true,
shipping_methods: true,
}, },
gift_cards: true,
items: {
variant: {
product: true,
},
},
payment_sessions: true,
region: { countries: true },
shipping_address: true,
shipping_methods: true,
}),
expect.objectContaining({
select: undefined, select: undefined,
where: { where: {
id: "withpays", id: "withpays",
@@ -907,7 +912,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cannot")) { if (q.where.id === IdMap.getId("cannot")) {
return Promise.resolve({ return Promise.resolve({
items: [ items: [
@@ -1019,7 +1024,7 @@ describe("CartService", () => {
}, },
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: () => Promise.resolve({}), findOneWithRelations: () => Promise.resolve({}),
}) })
const cartService = new CartService({ const cartService = new CartService({
manager: MockManager, manager: MockManager,
@@ -1091,7 +1096,7 @@ describe("CartService", () => {
describe("updateBillingAddress", () => { describe("updateBillingAddress", () => {
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: () => findOneWithRelations: () =>
Promise.resolve({ Promise.resolve({
region: { countries: [{ iso_2: "us" }] }, region: { countries: [{ iso_2: "us" }] },
}), }),
@@ -1153,7 +1158,7 @@ describe("CartService", () => {
describe("updateShippingAddress", () => { describe("updateShippingAddress", () => {
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: () => findOneWithRelations: () =>
Promise.resolve({ Promise.resolve({
region: { countries: [{ iso_2: "us" }] }, region: { countries: [{ iso_2: "us" }] },
}), }),
@@ -1288,7 +1293,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: () => findOneWithRelations: () =>
Promise.resolve({ Promise.resolve({
items: [ items: [
{ {
@@ -1415,7 +1420,7 @@ describe("CartService", () => {
describe("setPaymentSession", () => { describe("setPaymentSession", () => {
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cartWithLine")) { if (q.where.id === IdMap.getId("cartWithLine")) {
return Promise.resolve({ return Promise.resolve({
total: 100, total: 100,
@@ -1623,7 +1628,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("cart-to-filter")) { if (q.where.id === IdMap.getId("cart-to-filter")) {
return Promise.resolve(cart3) return Promise.resolve(cart3)
} }
@@ -1829,7 +1834,7 @@ describe("CartService", () => {
const cartWithCustomSO = buildCart("cart-with-custom-so") const cartWithCustomSO = buildCart("cart-with-custom-so")
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
switch (q.where.id) { switch (q.where.id) {
case IdMap.getId("lines"): case IdMap.getId("lines"):
return Promise.resolve(cart3) return Promise.resolve(cart3)
@@ -2028,7 +2033,7 @@ describe("CartService", () => {
} }
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
if (q.where.id === IdMap.getId("with-d")) { if (q.where.id === IdMap.getId("with-d")) {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("cart"), id: IdMap.getId("cart"),
@@ -2517,7 +2522,7 @@ describe("CartService", () => {
describe("removeDiscount", () => { describe("removeDiscount", () => {
const cartRepository = MockRepository({ const cartRepository = MockRepository({
findOne: (q) => { findOneWithRelations: (rel, q) => {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("cart"), id: IdMap.getId("cart"),
discounts: [ discounts: [
+4 -2
View File
@@ -231,13 +231,15 @@ class CartService extends TransactionBaseService {
const cartRepo = this.activeManager_.withRepository(this.cartRepository_) const cartRepo = this.activeManager_.withRepository(this.cartRepository_)
const query = buildQuery({ id: cartId }, options) const query = buildQuery({ id: cartId }, options)
query.relationLoadStrategy = "query"
if ((options.select || []).length === 0) { if ((options.select || []).length === 0) {
query.select = undefined 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) { if (!raw) {
throw new MedusaError( throw new MedusaError(