feat: Set withDeleted as true if filtering on deleted_at in modules (#6626)

Similar convention already existed in v1, so we are just applying this for the abstract module factory
This commit is contained in:
Stevche Radevski
2024-03-08 12:40:00 +01:00
committed by GitHub
parent 272fb6d328
commit 056e3e9599
4 changed files with 67 additions and 6 deletions

View File

@@ -140,7 +140,6 @@ export class AdminGetProductsParams extends extendedFindParamsMixin({
// @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
// include_category_children?: boolean
// TODO: The OperatorMap and DateOperator are slightly different, so the date comparisons is a breaking change.
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)

View File

@@ -765,6 +765,23 @@ describe("ProductModuleService products", function () {
}
})
it("should retrieve soft-deleted products if filtered on deleted_at", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0],
})
const products = await module.create([data])
await module.softDelete([products[0].id])
const softDeleted = await module.list({
deleted_at: { $gt: "01-01-2022" },
})
expect(softDeleted).toHaveLength(1)
})
it("should emit events through eventBus", async () => {
const eventBusSpy = jest.spyOn(EventBusService.prototype, "emit")
const data = buildProductAndRelationsData({

View File

@@ -595,12 +595,21 @@ export interface ProductOptionValueDTO {
* @prop categories - Filters on a product's categories.
* @prop collection_id - Filters a product by its associated collections.
*/
export interface FilterableProductProps
extends BaseFilterable<FilterableProductProps> {
/**
* Search through the products' attributes, such as titles and descriptions, using this search term.
*/
q?: string
/**
* The status to filter products by
*/
status?: ProductStatus | ProductStatus[]
/**
* The titles to filter products by.
*/
title?: string | string[]
/**
* The handles to filter products by.
*/
@@ -609,6 +618,10 @@ export interface FilterableProductProps
* The IDs to filter products by.
*/
id?: string | string[]
/**
* Filters only or excluding gift card products
*/
is_giftcard?: boolean
/**
* Filters on a product's tags.
*/
@@ -635,6 +648,10 @@ export interface FilterableProductProps
*/
is_active?: boolean
}
/**
* Filter a product by the ID of the associated type
*/
type_id?: string | string[]
/**
* Filter a product by the IDs of their associated categories.
*/
@@ -643,6 +660,18 @@ export interface FilterableProductProps
* Filters a product by the IDs of their associated collections.
*/
collection_id?: string | string[] | OperatorMap<string>
/**
* Filters a product based on when it was created
*/
created_at?: OperatorMap<string>
/**
* Filters a product based on when it was updated
*/
updated_at?: OperatorMap<string>
/**
* Filters soft-deleted products based on the date they were deleted at.
*/
deleted_at?: OperatorMap<string>
}
/**

View File

@@ -3,12 +3,20 @@ import { deduplicate, isDefined, isObject } from "../common"
import { SoftDeletableFilterKey } from "../dal"
// Following convention here is fine, we can make it configurable if needed.
const DELETED_AT_FIELD_NAME = "deleted_at"
type FilterFlags = {
withDeleted?: boolean
}
export function buildQuery<T = any, TDto = any>(
filters: Record<string, any> = {},
config: FindConfig<TDto> & { primaryKeyFields?: string | string[] } = {}
): DAL.FindOptions<T> {
const where: DAL.FilterQuery<T> = {}
buildWhere(filters, where)
const filterFlags: FilterFlags = {}
buildWhere(filters, where, filterFlags)
const primaryKeyFieldArray = isDefined(config.primaryKeyFields)
? !Array.isArray(config.primaryKeyFields)
@@ -43,7 +51,7 @@ export function buildQuery<T = any, TDto = any>(
findOptions.orderBy = config.order as DAL.OptionsQuery<T>["orderBy"]
}
if (config.withDeleted) {
if (config.withDeleted || filterFlags.withDeleted) {
findOptions.filters ??= {}
findOptions.filters[SoftDeletableFilterKey] = {
withDeleted: true,
@@ -61,12 +69,20 @@ export function buildQuery<T = any, TDto = any>(
return { where, options: findOptions }
}
function buildWhere(filters: Record<string, any> = {}, where = {}) {
function buildWhere(
filters: Record<string, any> = {},
where = {},
flags: FilterFlags = {}
) {
for (let [prop, value] of Object.entries(filters)) {
if (prop === DELETED_AT_FIELD_NAME) {
flags.withDeleted = true
}
if (["$or", "$and"].includes(prop)) {
where[prop] = value.map((val) => {
const deepWhere = {}
buildWhere(val, deepWhere)
buildWhere(val, deepWhere, flags)
return deepWhere
})
continue
@@ -80,7 +96,7 @@ function buildWhere(filters: Record<string, any> = {}, where = {}) {
if (isObject(value)) {
where[prop] = {}
buildWhere(value, where[prop])
buildWhere(value, where[prop], flags)
continue
}