feat(medusa): Allow to filter collections by discount condition id (#2411)

This commit is contained in:
Adrien de Peretti
2022-10-11 16:14:10 +02:00
committed by GitHub
parent 308b99cc5d
commit c717442451
11 changed files with 210 additions and 51 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): Allow to filter collections by discount condition id

View File

@@ -5,8 +5,23 @@ const { initDb, useDb } = require("../../../helpers/use-db")
const productSeeder = require("../../helpers/product-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
const {
DiscountRuleType,
AllocationType,
DiscountConditionType,
DiscountConditionOperator,
} = require("@medusajs/medusa")
const { IdMap } = require("medusa-test-utils")
const { simpleDiscountFactory } = require("../../factories")
jest.setTimeout(30000)
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
describe("/admin/collections", () => {
let medusaProcess
let dbConnection
@@ -334,5 +349,82 @@ describe("/admin/collections", () => {
offset: 0,
})
})
it("returns a list of collections filtered by discount condition id", async () => {
const api = useApi()
const resCollections = await api.get("/admin/collections", adminReqConfig)
const collection1 = resCollections.data.collections[0]
const collection2 = resCollections.data.collections[1]
const buildDiscountData = (code, conditionId, collections) => {
return {
code,
rule: {
type: DiscountRuleType.PERCENTAGE,
value: 10,
allocation: AllocationType.TOTAL,
conditions: [
{
id: conditionId,
type: DiscountConditionType.PRODUCT_COLLECTIONS,
operator: DiscountConditionOperator.IN,
product_collections: collections,
},
],
},
}
}
const discountConditionId = IdMap.getId("discount-condition-type-1")
await simpleDiscountFactory(
dbConnection,
buildDiscountData("code-1", discountConditionId, [collection1.id])
)
const discountConditionId2 = IdMap.getId("discount-condition-type-2")
await simpleDiscountFactory(
dbConnection,
buildDiscountData("code-2", discountConditionId2, [collection2.id])
)
let res = await api.get(
`/admin/collections?discount_condition_id=${discountConditionId}`,
adminReqConfig
)
expect(res.status).toEqual(200)
expect(res.data.collections).toHaveLength(1)
expect(res.data.collections).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: collection1.id }),
])
)
res = await api.get(
`/admin/collections?discount_condition_id=${discountConditionId2}`,
adminReqConfig
)
expect(res.status).toEqual(200)
expect(res.data.collections).toHaveLength(1)
expect(res.data.collections).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: collection2.id }),
])
)
res = await api.get(`/admin/collections`, adminReqConfig)
expect(res.status).toEqual(200)
expect(res.data.collections).toHaveLength(3)
expect(res.data.collections).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: collection1.id }),
expect.objectContaining({ id: collection2.id }),
])
)
})
})
})

View File

@@ -6,10 +6,10 @@ Object {
Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"handle": "test-collection2",
"id": "test-collection2",
"metadata": null,
"title": "Test collection",
"title": "Test collection 2",
"updated_at": Any<String>,
},
Object {
@@ -24,10 +24,10 @@ Object {
Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection2",
"id": "test-collection2",
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"title": "Test collection 2",
"title": "Test collection",
"updated_at": Any<String>,
},
],

View File

@@ -66,7 +66,7 @@ describe("/store/collections", () => {
expect(response.data).toMatchSnapshot({
collections: [
{
id: "test-collection",
id: "test-collection2",
created_at: expect.any(String),
updated_at: expect.any(String),
},
@@ -76,7 +76,7 @@ describe("/store/collections", () => {
updated_at: expect.any(String),
},
{
id: "test-collection2",
id: "test-collection",
created_at: expect.any(String),
updated_at: expect.any(String),
},

View File

@@ -1,6 +1,5 @@
import { IsNumber, IsOptional, IsString, ValidateNested } from "class-validator"
import { Request, Response } from "express"
import _, { identity } from "lodash"
import { DateComparisonOperator } from "../../../../types/common"
import ProductCollectionService from "../../../../services/product-collection"
@@ -18,6 +17,7 @@ import { Type } from "class-transformer"
* - (query) title {string} The title of collections to return.
* - (query) handle {string} The handle of collections to return.
* - (query) q {string} a search term to search titles and handles.
* - (query) discount_condition_id {string} The discount condition id on which to filter the product collections.
* - in: query
* name: created_at
* description: Date comparison for when resulting collections were created.
@@ -143,22 +143,19 @@ export default async (req: Request, res: Response) => {
"productCollectionService"
)
const {
validatedQuery: { limit, offset },
filterableFields,
listConfig,
} = req
const { filterableFields, listConfig } = req
const { skip, take } = listConfig
const [collections, count] = await productCollectionService.listAndCount(
_.pickBy(filterableFields, identity),
filterableFields,
listConfig
)
res.status(200).json({
collections,
count,
offset,
limit,
offset: skip,
limit: take,
})
}
@@ -202,4 +199,8 @@ export class AdminGetCollectionsParams extends AdminGetCollectionsPaginationPara
@IsString()
@IsOptional()
q?: string
@IsString()
@IsOptional()
discount_condition_id?: string
}

View File

@@ -3,7 +3,6 @@ import {
StringComparisonOperator,
} from "../../../../types/common"
import { IsNumber, IsOptional, IsString } from "class-validator"
import { identity, pickBy } from "lodash"
import { IsType } from "../../../../utils/validators/is-type"
import ProductTypeService from "../../../../services/product-type"
@@ -143,7 +142,7 @@ export default async (req, res) => {
const { skip, take } = req.listConfig
const [types, count] = await typeService.listAndCount(
pickBy(filterableFields, identity),
filterableFields,
listConfig
)

View File

@@ -36,6 +36,11 @@ describe("GET /store/collections", () => {
expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledWith(
{},
{
order: {
created_at: "DESC",
},
relations: [],
select: undefined,
skip: 0,
take: 5,
}
@@ -60,6 +65,11 @@ describe("GET /store/collections", () => {
expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledWith(
{},
{
order: {
created_at: "DESC",
},
relations: [],
select: undefined,
skip: 10,
take: 10,
}
@@ -84,6 +94,11 @@ describe("GET /store/collections", () => {
expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledWith(
{},
{
order: {
created_at: "DESC",
},
relations: [],
select: undefined,
skip: 10,
take: 20,
}

View File

@@ -1,21 +1,38 @@
import { Router } from "express"
import { PaginatedResponse } from "./../../../../types/common"
import { PaginatedResponse } from "../../../../types/common"
import { ProductCollection } from "../../../../"
import middlewares from "../../../middlewares"
import middlewares, { transformQuery } from "../../../middlewares"
import { StoreGetCollectionsParams } from "./list-collections"
const route = Router()
export default (app) => {
app.use("/collections", route)
route.get("/", middlewares.wrap(require("./list-collections").default))
route.get(
"/",
transformQuery(StoreGetCollectionsParams, {
allowedFields,
isList: true,
}),
middlewares.wrap(require("./list-collections").default)
)
route.get("/:id", middlewares.wrap(require("./get-collection").default))
return app
}
export const defaultStoreCollectionFields = ["id", "title", "handle"]
export const defaultStoreCollectionRelations = ["products"]
export const allowedFields = [
"id",
"title",
"handle",
"metadata",
"created_at",
"updated_at",
"deleted_at",
...defaultStoreCollectionRelations,
]
export type StoreCollectionsListRes = PaginatedResponse & {
collections: ProductCollection[]

View File

@@ -3,7 +3,6 @@ import { IsInt, IsOptional, ValidateNested } from "class-validator"
import { DateComparisonOperator } from "../../../../types/common"
import ProductCollectionService from "../../../../services/product-collection"
import { Type } from "class-transformer"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /collections
@@ -105,24 +104,19 @@ import { validator } from "../../../../utils/validator"
* $ref: "#/components/responses/500_error"
*/
export default async (req, res) => {
const validated = await validator(StoreGetCollectionsParams, req.query)
const { limit, offset, ...filterableFields } = validated
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const listConfig = {
skip: offset,
take: limit,
}
const { listConfig, filterableFields } = req
const { skip, take } = req.listConfig
const [collections, count] = await productCollectionService.listAndCount(
filterableFields,
listConfig
)
res.status(200).json({ collections, count, limit, offset })
res.status(200).json({ collections, count, limit: take, offset: skip })
}
export class StoreGetCollectionsParams {

View File

@@ -1,6 +1,36 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductCollection } from "../models/product-collection"
import { ProductCollection } from "../models"
import { ExtendedFindConfig } from "../types/common"
@EntityRepository(ProductCollection)
// eslint-disable-next-line max-len
export class ProductCollectionRepository extends Repository<ProductCollection> {}
export class ProductCollectionRepository extends Repository<ProductCollection> {
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductCollection, Partial<ProductCollection>>
): Promise<[ProductCollection[], number]> {
const qb = this.createQueryBuilder("pc")
if (query?.select) {
qb.select(query.select.map((select) => `pc.${select}`))
}
if (query.skip) {
qb.skip(query.skip)
}
if (query.take) {
qb.take(query.take)
}
return await qb
.where(query.where)
.innerJoin(
"discount_condition_product_collection",
"dc_pc",
`dc_pc.product_collection_id = pc.id AND dc_pc.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
}
}

View File

@@ -4,12 +4,12 @@ import { TransactionBaseService } from "../interfaces"
import { ProductCollection } from "../models"
import { ProductRepository } from "../repositories/product"
import { ProductCollectionRepository } from "../repositories/product-collection"
import { ExtendedFindConfig, FindConfig, QuerySelector } from "../types/common"
import { FindConfig } from "../types/common"
import {
CreateProductCollection,
UpdateProductCollection,
} from "../types/product-collection"
import { buildQuery, setMetadata } from "../utils"
import { buildQuery, isString, setMetadata } from "../utils"
import { formatException } from "../utils/exception-formatter"
import EventBusService from "./event-bus"
@@ -219,15 +219,14 @@ class ProductCollectionService extends TransactionBaseService {
* @return the result of the find operation
*/
async list(
selector = {},
selector: Partial<ProductCollection> & {
q?: string
discount_condition_id?: string
} = {},
config = { skip: 0, take: 20 }
): Promise<ProductCollection[]> {
const productCollectionRepo = this.manager_.getCustomRepository(
this.productCollectionRepository_
)
const query = buildQuery(selector, config)
return await productCollectionRepo.find(query)
const [collections] = await this.listAndCount(selector, config)
return collections
}
/**
@@ -237,7 +236,10 @@ class ProductCollectionService extends TransactionBaseService {
* @return the result of the find operation
*/
async listAndCount(
selector: QuerySelector<ProductCollection> = {},
selector: Partial<ProductCollection> & {
q?: string
discount_condition_id?: string
} = {},
config: FindConfig<ProductCollection> = { skip: 0, take: 20 }
): Promise<[ProductCollection[], number]> {
const productCollectionRepo = this.manager_.getCustomRepository(
@@ -245,17 +247,12 @@ class ProductCollectionService extends TransactionBaseService {
)
let q
if ("q" in selector) {
if (isString(selector.q)) {
q = selector.q
delete selector.q
}
const query = buildQuery(
selector,
config
) as ExtendedFindConfig<ProductCollection> & {
where: (qb: any) => void
}
const query = buildQuery(selector, config)
if (q) {
const where = query.where
@@ -278,6 +275,15 @@ class ProductCollectionService extends TransactionBaseService {
}
}
if (query.where.discount_condition_id) {
const discountConditionId = query.where.discount_condition_id as string
delete query.where.discount_condition_id
return await productCollectionRepo.findAndCountByDiscountConditionId(
discountConditionId,
query
)
}
return await productCollectionRepo.findAndCount(query)
}
}