hotfix(medusa): Optimize product retrieval (#185)
This commit is contained in:
@@ -14,13 +14,23 @@ export default async (req, res) => {
|
|||||||
selector.q = req.query.q
|
selector.q = req.query.q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let includeFields = []
|
||||||
|
if ("fields" in req.query) {
|
||||||
|
includeFields = req.query.fields.split(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandFields = []
|
||||||
|
if ("expand" in req.query) {
|
||||||
|
expandFields = req.query.expand.split(",")
|
||||||
|
}
|
||||||
|
|
||||||
if ("is_giftcard" in req.query) {
|
if ("is_giftcard" in req.query) {
|
||||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
const listConfig = {
|
const listConfig = {
|
||||||
select: defaultFields,
|
select: includeFields.length ? includeFields : defaultFields,
|
||||||
relations: defaultRelations,
|
relations: expandFields.length ? expandFields : defaultRelations,
|
||||||
skip: offset,
|
skip: offset,
|
||||||
take: limit,
|
take: limit,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,53 @@
|
|||||||
import { EntityRepository, Repository } from "typeorm"
|
import { flatten, groupBy, map, merge } from "lodash"
|
||||||
|
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||||
import { Product } from "../models/product"
|
import { Product } from "../models/product"
|
||||||
|
|
||||||
@EntityRepository(Product)
|
@EntityRepository(Product)
|
||||||
export class ProductRepository extends Repository<Product> {}
|
export class ProductRepository extends Repository<Product> {
|
||||||
|
public async findWithRelations(
|
||||||
|
relations: Array<keyof Product> = [],
|
||||||
|
optionsWithoutRelations: Omit<FindManyOptions<Product>, "relations"> = {}
|
||||||
|
): Promise<Product[]> {
|
||||||
|
const entities = await this.find(optionsWithoutRelations)
|
||||||
|
const entitiesIds = entities.map(({ id }) => id)
|
||||||
|
|
||||||
|
const groupedRelations = {}
|
||||||
|
for (const rel of relations) {
|
||||||
|
const [topLevel] = rel.split(".")
|
||||||
|
if (groupedRelations[topLevel]) {
|
||||||
|
groupedRelations[topLevel].push(rel)
|
||||||
|
} else {
|
||||||
|
groupedRelations[topLevel] = [rel]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entitiesIdsWithRelations = await Promise.all(
|
||||||
|
Object.entries(groupedRelations).map(([_, rels]) => {
|
||||||
|
return this.findByIds(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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneWithRelations(
|
||||||
|
relations: Array<keyof Product> = [],
|
||||||
|
optionsWithoutRelations: Omit<FindManyOptions<Product>, "relations"> = {}
|
||||||
|
): Promise<Product> {
|
||||||
|
// Limit 1
|
||||||
|
optionsWithoutRelations.take = 1
|
||||||
|
|
||||||
|
const result = await this.findWithRelations(
|
||||||
|
relations,
|
||||||
|
optionsWithoutRelations
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const eventBusService = {
|
|||||||
describe("ProductService", () => {
|
describe("ProductService", () => {
|
||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
const productRepo = MockRepository({
|
const productRepo = MockRepository({
|
||||||
findOne: () => Promise.resolve({ id: IdMap.getId("ironman") }),
|
findOneWithRelations: () =>
|
||||||
|
Promise.resolve({ id: IdMap.getId("ironman") }),
|
||||||
})
|
})
|
||||||
const productService = new ProductService({
|
const productService = new ProductService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
@@ -25,8 +26,8 @@ describe("ProductService", () => {
|
|||||||
it("successfully retrieves a product", async () => {
|
it("successfully retrieves a product", async () => {
|
||||||
const result = await productService.retrieve(IdMap.getId("ironman"))
|
const result = await productService.retrieve(IdMap.getId("ironman"))
|
||||||
|
|
||||||
expect(productRepo.findOne).toHaveBeenCalledTimes(1)
|
expect(productRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||||
expect(productRepo.findOne).toHaveBeenCalledWith({
|
expect(productRepo.findOneWithRelations).toHaveBeenCalledWith(undefined, {
|
||||||
where: { id: IdMap.getId("ironman") },
|
where: { id: IdMap.getId("ironman") },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ describe("ProductService", () => {
|
|||||||
options: [],
|
options: [],
|
||||||
collection: { id: IdMap.getId("cat"), title: "Suits" },
|
collection: { id: IdMap.getId("cat"), title: "Suits" },
|
||||||
}),
|
}),
|
||||||
findOne: () => ({
|
findOneWithRelations: () => ({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
title: "Suit",
|
title: "Suit",
|
||||||
options: [],
|
options: [],
|
||||||
@@ -137,7 +138,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query => {
|
findOneWithRelations: (rels, query) => {
|
||||||
if (query.where.id === IdMap.getId("ironman&co")) {
|
if (query.where.id === IdMap.getId("ironman&co")) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("ironman&co"),
|
id: IdMap.getId("ironman&co"),
|
||||||
@@ -322,7 +323,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("addOption", () => {
|
describe("addOption", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query =>
|
findOneWithRelations: query =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
options: [{ title: "Color" }],
|
options: [{ title: "Color" }],
|
||||||
@@ -395,7 +396,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("reorderVariants", () => {
|
describe("reorderVariants", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query =>
|
findOneWithRelations: query =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }],
|
variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }],
|
||||||
@@ -453,7 +454,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("reorderOptions", () => {
|
describe("reorderOptions", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query =>
|
findOneWithRelations: query =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
options: [
|
options: [
|
||||||
@@ -519,7 +520,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("updateOption", () => {
|
describe("updateOption", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query =>
|
findOneWithRelations: query =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
options: [
|
options: [
|
||||||
@@ -594,7 +595,7 @@ describe("ProductService", () => {
|
|||||||
|
|
||||||
describe("deleteOption", () => {
|
describe("deleteOption", () => {
|
||||||
const productRepository = MockRepository({
|
const productRepository = MockRepository({
|
||||||
findOne: query =>
|
findOneWithRelations: query =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
id: IdMap.getId("ironman"),
|
id: IdMap.getId("ironman"),
|
||||||
variants: [
|
variants: [
|
||||||
|
|||||||
@@ -93,6 +93,17 @@ class ProductService extends BaseService {
|
|||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
const query = this.buildQuery_(selector, config)
|
||||||
|
|
||||||
|
if (config.relations && config.relations.length > 0) {
|
||||||
|
query.relations = config.relations
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.select && config.select.length > 0) {
|
||||||
|
query.select = config.select
|
||||||
|
}
|
||||||
|
|
||||||
|
const rels = query.relations
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
const where = query.where
|
const where = query.where
|
||||||
|
|
||||||
@@ -122,7 +133,7 @@ class ProductService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return productRepo.find(query)
|
return productRepo.findWithRelations(rels, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,20 +154,33 @@ class ProductService extends BaseService {
|
|||||||
* @return {Promise<Product>} the result of the find one operation.
|
* @return {Promise<Product>} the result of the find one operation.
|
||||||
*/
|
*/
|
||||||
async retrieve(productId, config = {}) {
|
async retrieve(productId, config = {}) {
|
||||||
return this.atomicPhase_(async manager => {
|
const productRepo = this.manager_.getCustomRepository(
|
||||||
const productRepo = manager.getCustomRepository(this.productRepository_)
|
this.productRepository_
|
||||||
const validatedId = this.validateId_(productId)
|
)
|
||||||
const query = this.buildQuery_({ id: validatedId }, config)
|
const validatedId = this.validateId_(productId)
|
||||||
const product = await productRepo.findOne(query)
|
|
||||||
if (!product) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Product with id: ${productId} was not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return product
|
const query = { where: { id: validatedId } }
|
||||||
})
|
|
||||||
|
if (config.relations && config.relations.length > 0) {
|
||||||
|
query.relations = config.relations
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.select && config.select.length > 0) {
|
||||||
|
query.select = config.select
|
||||||
|
}
|
||||||
|
|
||||||
|
const rels = query.relations
|
||||||
|
delete query.relations
|
||||||
|
const product = await productRepo.findOneWithRelations(rels, query)
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Product with id: ${productId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return product
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user