feat: Allow retrieval of soft-deleted products (#723)
This commit is contained in:
@@ -144,6 +144,9 @@ describe("/admin/products", () => {
|
||||
const notExpected = [
|
||||
expect.objectContaining({ status: "draft" }),
|
||||
expect.objectContaining({ status: "rejected" }),
|
||||
expect.objectContaining({
|
||||
id: "test-product_filtering_4",
|
||||
}),
|
||||
]
|
||||
|
||||
const response = await api
|
||||
@@ -175,6 +178,48 @@ describe("/admin/products", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("returns a list of deleted products with free text query", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.get("/admin/products?deleted_at[gt]=01-26-1990&q=test", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.products).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-product_filtering_4",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("returns a list of deleted products", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.get("/admin/products?deleted_at[gt]=01-26-1990", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.products).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-product_filtering_4",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("returns a list of products in collection", async () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -271,4 +271,16 @@ module.exports = async (connection, data = {}) => {
|
||||
})
|
||||
|
||||
await manager.save(product3)
|
||||
|
||||
const product4 = manager.create(Product, {
|
||||
id: "test-product_filtering_4",
|
||||
handle: "test-product_filtering_4",
|
||||
title: "Test product filtering 4",
|
||||
profile_id: defaultProfile.id,
|
||||
description: "test-product-description",
|
||||
status: "proposed",
|
||||
deleted_at: new Date().toISOString(),
|
||||
})
|
||||
|
||||
await manager.save(product4)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { In, FindOperator, Raw } from "typeorm"
|
||||
import { FindOperator, In, Raw } from "typeorm"
|
||||
|
||||
/**
|
||||
* Common functionality for Services
|
||||
@@ -63,13 +63,17 @@ class BaseService {
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
const query = {
|
||||
where: build(selector),
|
||||
}
|
||||
|
||||
if ("deleted_at" in selector) {
|
||||
query.withDeleted = true
|
||||
}
|
||||
|
||||
if ("skip" in config) {
|
||||
query.skip = config.skip
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import {
|
||||
OrderByCondition,
|
||||
EntityRepository,
|
||||
FindManyOptions,
|
||||
OrderByCondition,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { Product } from "../models/product"
|
||||
@@ -14,6 +14,7 @@ type CustomOptions = {
|
||||
order?: OrderByCondition
|
||||
skip?: number
|
||||
take?: number
|
||||
withDeleted?: boolean
|
||||
}
|
||||
|
||||
type FindWithRelationsOptions = CustomOptions
|
||||
@@ -31,7 +32,7 @@ export class ProductRepository extends Repository<Product> {
|
||||
|
||||
private async queryProducts(
|
||||
optionsWithoutRelations: FindWithRelationsOptions,
|
||||
shouldCount: boolean = false
|
||||
shouldCount = false
|
||||
): Promise<[Product[], number]> {
|
||||
const tags = optionsWithoutRelations.where.tags
|
||||
delete optionsWithoutRelations.where.tags
|
||||
@@ -48,6 +49,10 @@ export class ProductRepository extends Repository<Product> {
|
||||
.andWhere(`tags.id IN (:...ids)`, { ids: tags._value })
|
||||
}
|
||||
|
||||
if (optionsWithoutRelations.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
let entities: Product[]
|
||||
let count = null
|
||||
if (shouldCount) {
|
||||
@@ -79,7 +84,8 @@ export class ProductRepository extends Repository<Product> {
|
||||
|
||||
private async queryProductsWithIds(
|
||||
entityIds: string[],
|
||||
groupedRelations: { [toplevel: string]: string[] }
|
||||
groupedRelations: { [toplevel: string]: string[] },
|
||||
withDeleted = false
|
||||
): Promise<Product[]> {
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(([toplevel, rels]) => {
|
||||
@@ -114,12 +120,22 @@ export class ProductRepository extends Repository<Product> {
|
||||
)
|
||||
}
|
||||
|
||||
return querybuilder
|
||||
.where(
|
||||
if (withDeleted) {
|
||||
querybuilder = querybuilder
|
||||
.where("products.id IN (:...entitiesIds)", {
|
||||
entitiesIds: entityIds,
|
||||
})
|
||||
.withDeleted()
|
||||
} else {
|
||||
querybuilder = querybuilder.where(
|
||||
"products.deleted_at IS NULL AND products.id IN (:...entitiesIds)",
|
||||
{ entitiesIds: entityIds }
|
||||
{
|
||||
entitiesIds: entityIds,
|
||||
}
|
||||
)
|
||||
.getMany()
|
||||
}
|
||||
|
||||
return querybuilder.getMany()
|
||||
})
|
||||
).then(flatten)
|
||||
|
||||
@@ -133,7 +149,9 @@ export class ProductRepository extends Repository<Product> {
|
||||
let count: number
|
||||
let entities: Product[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
|
||||
})
|
||||
count = entities.length
|
||||
} else {
|
||||
const result = await this.queryProducts(
|
||||
@@ -161,8 +179,10 @@ export class ProductRepository extends Repository<Product> {
|
||||
const groupedRelations = this.getGroupedRelations(relations)
|
||||
const entitiesIdsWithRelations = await this.queryProductsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations
|
||||
groupedRelations,
|
||||
idsOrOptionsWithoutRelations.withDeleted
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
this.mergeEntitiesWithRelations(entitiesAndRelations)
|
||||
@@ -172,11 +192,14 @@ export class ProductRepository extends Repository<Product> {
|
||||
|
||||
public async findWithRelations(
|
||||
relations: Array<keyof Product> = [],
|
||||
idsOrOptionsWithoutRelations: FindWithRelationsOptions = {}
|
||||
idsOrOptionsWithoutRelations: FindWithRelationsOptions = {},
|
||||
withDeleted = false
|
||||
): Promise<Product[]> {
|
||||
let entities: Product[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted,
|
||||
})
|
||||
} else {
|
||||
const result = await this.queryProducts(
|
||||
idsOrOptionsWithoutRelations,
|
||||
@@ -198,8 +221,10 @@ export class ProductRepository extends Repository<Product> {
|
||||
const groupedRelations = this.getGroupedRelations(relations)
|
||||
const entitiesIdsWithRelations = await this.queryProductsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations
|
||||
groupedRelations,
|
||||
withDeleted
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
this.mergeEntitiesWithRelations(entitiesAndRelations)
|
||||
|
||||
@@ -106,7 +106,8 @@ class ProductService extends BaseService {
|
||||
const raw = await qb.getMany()
|
||||
return productRepo.findWithRelations(
|
||||
relations,
|
||||
raw.map((i) => i.id)
|
||||
raw.map((i) => i.id),
|
||||
query.withDeleted ?? false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -140,7 +141,8 @@ class ProductService extends BaseService {
|
||||
|
||||
const products = await productRepo.findWithRelations(
|
||||
relations,
|
||||
raw.map((i) => i.id)
|
||||
raw.map((i) => i.id),
|
||||
query.withDeleted ?? false
|
||||
)
|
||||
return [products, count]
|
||||
}
|
||||
@@ -799,7 +801,7 @@ class ProductService extends BaseService {
|
||||
delete where.description
|
||||
delete where.title
|
||||
|
||||
return productRepo
|
||||
let qb = productRepo
|
||||
.createQueryBuilder("product")
|
||||
.leftJoinAndSelect("product.variants", "variant")
|
||||
.leftJoinAndSelect("product.collection", "collection")
|
||||
@@ -814,6 +816,12 @@ class ProductService extends BaseService {
|
||||
.orWhere(`collection.title ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
|
||||
if (query.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
return qb
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user