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

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): Cart custom query strategy

View File

@@ -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",

View File

@@ -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")

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 { 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

View File

@@ -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: [

View File

@@ -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(