Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(medusa): Cart custom query strategy #4083

Merged
merged 7 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-eels-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---

feat(medusa): Cart custom query strategy
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 8 additions & 0 deletions packages/medusa/src/models/cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
*/

import {
AfterLoad,
BeforeInsert,
Column,
Entity,
Expand Down Expand Up @@ -390,6 +391,13 @@ export class Cart extends SoftDeletableEntity {
gift_card_total?: number
gift_card_tax_total?: number

@AfterLoad()
private afterLoad(): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment: We can now bring this back, as we are no longer using the query strategy.

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")
Expand Down
52 changes: 46 additions & 6 deletions packages/medusa/src/repositories/cart.ts
Original file line number Diff line number Diff line change
@@ -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
85 changes: 45 additions & 40 deletions packages/medusa/src/services/__tests__/cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
}
)
})
})

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -164,7 +169,7 @@ describe("CartService", () => {

const addressRepository = MockRepository({
create: (c) => c,
findOne: (id) => {
findOneWithRelations: (id) => {
return {
id,
first_name: "LeBron",
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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,
},
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,
},
payment_sessions: true,
region: { countries: true },
shipping_address: true,
shipping_methods: true,
}),
expect.objectContaining({
select: undefined,
where: {
id: "withpays",
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -1019,7 +1024,7 @@ describe("CartService", () => {
},
}
const cartRepository = MockRepository({
findOne: () => Promise.resolve({}),
findOneWithRelations: () => Promise.resolve({}),
})
const cartService = new CartService({
manager: MockManager,
Expand Down Expand Up @@ -1091,7 +1096,7 @@ describe("CartService", () => {

describe("updateBillingAddress", () => {
const cartRepository = MockRepository({
findOne: () =>
findOneWithRelations: () =>
Promise.resolve({
region: { countries: [{ iso_2: "us" }] },
}),
Expand Down Expand Up @@ -1153,7 +1158,7 @@ describe("CartService", () => {

describe("updateShippingAddress", () => {
const cartRepository = MockRepository({
findOne: () =>
findOneWithRelations: () =>
Promise.resolve({
region: { countries: [{ iso_2: "us" }] },
}),
Expand Down Expand Up @@ -1288,7 +1293,7 @@ describe("CartService", () => {
}

const cartRepository = MockRepository({
findOne: () =>
findOneWithRelations: () =>
Promise.resolve({
items: [
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -2517,7 +2522,7 @@ describe("CartService", () => {

describe("removeDiscount", () => {
const cartRepository = MockRepository({
findOne: (q) => {
findOneWithRelations: (rel, q) => {
return Promise.resolve({
id: IdMap.getId("cart"),
discounts: [
Expand Down
6 changes: 4 additions & 2 deletions packages/medusa/src/services/cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down