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